ckanext-search-tweaks 0.6.0a1__tar.gz → 0.6.1.post1__tar.gz
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-0.6.0a1/ckanext_search_tweaks.egg-info → ckanext_search_tweaks-0.6.1.post1}/PKG-INFO +5 -4
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext/search_tweaks/advanced_search/plugin.py +4 -4
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext/search_tweaks/field_relevance/plugin.py +2 -1
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext/search_tweaks/plugin.py +7 -8
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext/search_tweaks/query_popularity/logic/action.py +28 -7
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext/search_tweaks/query_popularity/logic/auth.py +8 -4
- ckanext_search_tweaks-0.6.1.post1/ckanext/search_tweaks/query_popularity/logic/schema.py +10 -0
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext/search_tweaks/query_popularity/plugin.py +13 -9
- ckanext_search_tweaks-0.6.1.post1/ckanext/search_tweaks/query_popularity/score.py +46 -0
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext/search_tweaks/query_relevance/plugin.py +2 -1
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext/search_tweaks/query_relevance/storage.py +9 -9
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext/search_tweaks/spellcheck/helpers.py +8 -2
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext/search_tweaks/tests/spellcheck/test_plugin.py +2 -2
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext/search_tweaks/tests/test_plugin.py +15 -4
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1/ckanext_search_tweaks.egg-info}/PKG-INFO +5 -4
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext_search_tweaks.egg-info/SOURCES.txt +1 -1
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext_search_tweaks.egg-info/requires.txt +1 -0
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext_search_tweaks.egg-info/top_level.txt +1 -0
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/pyproject.toml +50 -5
- ckanext_search_tweaks-0.6.1.post1/requirements.txt +0 -0
- ckanext_search_tweaks-0.6.1.post1/setup.cfg +26 -0
- ckanext-search-tweaks-0.6.0a1/ckanext/search_tweaks/__init__.py +0 -3
- ckanext-search-tweaks-0.6.0a1/ckanext/search_tweaks/query_popularity/score.py +0 -165
- ckanext-search-tweaks-0.6.0a1/ckanext_search_tweaks.egg-info/namespace_packages.txt +0 -1
- ckanext-search-tweaks-0.6.0a1/setup.cfg +0 -66
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/LICENSE +0 -0
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/MANIFEST.in +0 -0
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/README.md +0 -0
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext/__init__.py +0 -0
- {ckanext-search-tweaks-0.6.0a1/ckanext/search_tweaks/advanced_search → ckanext_search_tweaks-0.6.1.post1/ckanext/search_tweaks}/__init__.py +0 -0
- {ckanext-search-tweaks-0.6.0a1/ckanext/search_tweaks/field_relevance → ckanext_search_tweaks-0.6.1.post1/ckanext/search_tweaks/advanced_search}/__init__.py +0 -0
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext/search_tweaks/advanced_search/assets/advanced-search.css +0 -0
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext/search_tweaks/advanced_search/assets/advanced-search.js +0 -0
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext/search_tweaks/advanced_search/assets/webassets.yml +0 -0
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext/search_tweaks/advanced_search/templates/advanced_search/search_form.html +0 -0
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext/search_tweaks/cli.py +0 -0
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext/search_tweaks/config.py +0 -0
- {ckanext-search-tweaks-0.6.0a1/ckanext/search_tweaks/query_popularity → ckanext_search_tweaks-0.6.1.post1/ckanext/search_tweaks/field_relevance}/__init__.py +0 -0
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext/search_tweaks/field_relevance/assets/search-tweaks-reflect-range-in-label.js +0 -0
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext/search_tweaks/field_relevance/assets/webassets.yml +0 -0
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext/search_tweaks/field_relevance/templates/search_tweaks/field_relevance/promote.html +0 -0
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext/search_tweaks/field_relevance/views.py +0 -0
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext/search_tweaks/interfaces.py +0 -0
- {ckanext-search-tweaks-0.6.0a1/ckanext/search_tweaks/query_popularity/logic → ckanext_search_tweaks-0.6.1.post1/ckanext/search_tweaks/query_popularity}/__init__.py +0 -0
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext/search_tweaks/query_popularity/config.py +0 -0
- {ckanext-search-tweaks-0.6.0a1/ckanext/search_tweaks/tests → ckanext_search_tweaks-0.6.1.post1/ckanext/search_tweaks/query_popularity/logic}/__init__.py +0 -0
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext/search_tweaks/query_relevance/__init__.py +0 -0
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext/search_tweaks/query_relevance/cli.py +0 -0
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext/search_tweaks/query_relevance/score.py +0 -0
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext/search_tweaks/shared.py +0 -0
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext/search_tweaks/spellcheck/__init__.py +0 -0
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext/search_tweaks/spellcheck/cli.py +0 -0
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext/search_tweaks/spellcheck/plugin.py +0 -0
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext/search_tweaks/spellcheck/templates/search_tweaks/did_you_mean.html +0 -0
- {ckanext-search-tweaks-0.6.0a1/ckanext/search_tweaks/tests/query_relevance → ckanext_search_tweaks-0.6.1.post1/ckanext/search_tweaks/tests}/__init__.py +0 -0
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext/search_tweaks/tests/conftest.py +0 -0
- {ckanext-search-tweaks-0.6.0a1/ckanext/search_tweaks/tests/spellcheck → ckanext_search_tweaks-0.6.1.post1/ckanext/search_tweaks/tests/query_relevance}/__init__.py +0 -0
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext/search_tweaks/tests/query_relevance/test_plugin.py +0 -0
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext/search_tweaks/tests/query_relevance/test_score.py +0 -0
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext/search_tweaks/tests/query_relevance/test_storage.py +0 -0
- /ckanext-search-tweaks-0.6.0a1/requirements.txt → /ckanext_search_tweaks-0.6.1.post1/ckanext/search_tweaks/tests/spellcheck/__init__.py +0 -0
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext_search_tweaks.egg-info/dependency_links.txt +0 -0
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext_search_tweaks.egg-info/entry_points.txt +0 -0
- {ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/setup.py +0 -0
@@ -1,10 +1,10 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: ckanext-search-tweaks
|
3
|
-
Version: 0.6.
|
4
|
-
|
5
|
-
|
6
|
-
Author-email: sergey.motornyuk@linkdigital.com.au
|
3
|
+
Version: 0.6.1.post1
|
4
|
+
Author-email: DataShades <datashades@linkdigital.com.au>, Sergey Motornyuk <sergey.motornyuk@linkdigital.com.au>
|
5
|
+
Maintainer-email: DataShades <datashades@linkdigital.com.au>
|
7
6
|
License: AGPL
|
7
|
+
Project-URL: Homepage, https://github.com/DataShades/ckanext-search-tweaks
|
8
8
|
Keywords: CKAN
|
9
9
|
Classifier: Development Status :: 4 - Beta
|
10
10
|
Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
|
@@ -15,6 +15,7 @@ Description-Content-Type: text/markdown
|
|
15
15
|
License-File: LICENSE
|
16
16
|
Requires-Dist: freezegun
|
17
17
|
Requires-Dist: typing_extensions>=4.0.0
|
18
|
+
Requires-Dist: ckanext-toolbelt>=0.4.11
|
18
19
|
Provides-Extra: advanced-search
|
19
20
|
|
20
21
|
[](https://github.com/DataShades/ckanext-search-tweaks/actions)
|
@@ -72,10 +72,10 @@ class AdvancedSearchPlugin(p.SingletonPlugin):
|
|
72
72
|
def configure(self, config):
|
73
73
|
try:
|
74
74
|
from ckanext.composite_search.interfaces import ICompositeSearch
|
75
|
-
except ImportError:
|
76
|
-
|
77
|
-
|
78
|
-
|
75
|
+
except ImportError as err:
|
76
|
+
msg = "ckanext-composite-search is not installed"
|
77
|
+
raise CkanConfigurationException(msg) from err
|
78
|
+
|
79
79
|
if not p.plugin_loaded("composite_search"):
|
80
80
|
msg = "Advanced search requires `composite_search` plugin"
|
81
81
|
raise CkanConfigurationException(msg)
|
@@ -5,8 +5,9 @@ from typing import Any
|
|
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
8
|
from ckanext.search_tweaks.interfaces import ISearchTweaks
|
9
|
+
from ckanext.search_tweaks.shared import feature_disabled
|
10
|
+
|
10
11
|
from . import views
|
11
12
|
|
12
13
|
CONFIG_BOOST_FN = "ckanext.search_tweaks.field_relevance.boost_function"
|
{ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext/search_tweaks/plugin.py
RENAMED
@@ -5,8 +5,10 @@ from typing import Any
|
|
5
5
|
|
6
6
|
import ckan.plugins as plugins
|
7
7
|
import ckan.plugins.toolkit as tk
|
8
|
-
|
8
|
+
|
9
|
+
from . import config
|
9
10
|
from .interfaces import ISearchTweaks
|
11
|
+
from .shared import feature_disabled
|
10
12
|
|
11
13
|
log = logging.getLogger(__name__)
|
12
14
|
CONFIG_PREFER_BOOST = "ckanext.search_tweaks.common.prefer_boost"
|
@@ -74,10 +76,7 @@ def _set_qf(search_params: dict[str, Any]) -> None:
|
|
74
76
|
|
75
77
|
|
76
78
|
def _set_fuzzy(search_params: dict[str, Any]) -> None:
|
77
|
-
if not config.fuzzy():
|
78
|
-
return
|
79
|
-
|
80
|
-
if feature_disabled("fuzzy", search_params):
|
79
|
+
if not config.fuzzy() or feature_disabled("fuzzy", search_params):
|
81
80
|
return
|
82
81
|
|
83
82
|
distance = _get_fuzzy_distance()
|
@@ -93,9 +92,9 @@ def _set_fuzzy(search_params: dict[str, Any]) -> None:
|
|
93
92
|
|
94
93
|
fuzzy_q = " ".join(
|
95
94
|
map(
|
96
|
-
lambda s:
|
97
|
-
|
98
|
-
|
95
|
+
lambda s: (
|
96
|
+
f"{s}~{distance}" if s.isalpha() and s not in ("AND", "OR", "TO") else s
|
97
|
+
),
|
99
98
|
q.split(),
|
100
99
|
),
|
101
100
|
)
|
@@ -1,14 +1,20 @@
|
|
1
1
|
from __future__ import annotations
|
2
|
+
|
2
3
|
from typing import Any
|
3
|
-
|
4
|
+
|
4
5
|
import ckan.plugins.toolkit as tk
|
6
|
+
from ckan import types
|
7
|
+
from ckan.logic import validate
|
5
8
|
|
6
9
|
from ckanext.search_tweaks.query_popularity.score import Score
|
7
10
|
|
11
|
+
from . import schema
|
12
|
+
|
8
13
|
|
9
14
|
@tk.side_effect_free
|
10
15
|
def search_tweaks_query_popularity_list(
|
11
|
-
context: types.Context,
|
16
|
+
context: types.Context,
|
17
|
+
data_dict: dict[str, Any],
|
12
18
|
) -> list[dict[str, Any]]:
|
13
19
|
score = Score()
|
14
20
|
|
@@ -22,17 +28,32 @@ def search_tweaks_query_popularity_list(
|
|
22
28
|
|
23
29
|
@tk.side_effect_free
|
24
30
|
def search_tweaks_query_popularity_export(
|
25
|
-
context: types.Context,
|
31
|
+
context: types.Context,
|
32
|
+
data_dict: dict[str, Any],
|
33
|
+
) -> list[Any]:
|
34
|
+
score = Score()
|
35
|
+
|
36
|
+
return score.export()
|
37
|
+
|
38
|
+
|
39
|
+
@validate(schema.query_popularity_import)
|
40
|
+
def search_tweaks_query_popularity_import(
|
41
|
+
context: types.Context,
|
42
|
+
data_dict: dict[str, Any],
|
26
43
|
) -> dict[str, Any]:
|
44
|
+
tk.check_access("sysadmin", context, data_dict)
|
27
45
|
score = Score()
|
28
46
|
|
29
|
-
|
30
|
-
|
47
|
+
if tk.asbool(data_dict.get("reset")):
|
48
|
+
score.reset()
|
49
|
+
score.restore(data_dict["snapshot"])
|
50
|
+
score.refresh()
|
51
|
+
return {"success": True}
|
31
52
|
|
32
53
|
|
33
|
-
@tk.side_effect_free
|
34
54
|
def search_tweaks_query_popularity_ignore(
|
35
|
-
context: types.Context,
|
55
|
+
context: types.Context,
|
56
|
+
data_dict: dict[str, Any],
|
36
57
|
):
|
37
58
|
q = tk.get_or_bust(data_dict, "q")
|
38
59
|
score = Score()
|
@@ -1,23 +1,27 @@
|
|
1
1
|
from __future__ import annotations
|
2
|
+
|
2
3
|
from typing import Any
|
3
|
-
from ckan import types
|
4
4
|
|
5
|
+
from ckan import types
|
5
6
|
from ckan.authz import is_authorized
|
6
7
|
|
7
8
|
|
8
9
|
def search_tweaks_query_popularity_list(
|
9
|
-
context: types.Context,
|
10
|
+
context: types.Context,
|
11
|
+
data_dict: dict[str, Any],
|
10
12
|
) -> types.AuthResult:
|
11
13
|
return is_authorized("sysadmin", context, data_dict)
|
12
14
|
|
13
15
|
|
14
16
|
def search_tweaks_query_popularity_export(
|
15
|
-
context: types.Context,
|
17
|
+
context: types.Context,
|
18
|
+
data_dict: dict[str, Any],
|
16
19
|
) -> types.AuthResult:
|
17
20
|
return is_authorized("sysadmin", context, data_dict)
|
18
21
|
|
19
22
|
|
20
23
|
def search_tweaks_query_popularity_ignore(
|
21
|
-
context: types.Context,
|
24
|
+
context: types.Context,
|
25
|
+
data_dict: dict[str, Any],
|
22
26
|
) -> types.AuthResult:
|
23
27
|
return is_authorized("sysadmin", context, data_dict)
|
@@ -0,0 +1,10 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from ckan.logic.schema import validator_args
|
4
|
+
|
5
|
+
@validator_args
|
6
|
+
def query_popularity_import(not_empty, boolean_validator, convert_to_json_if_string):
|
7
|
+
return {
|
8
|
+
"snapshot": [not_empty, convert_to_json_if_string],
|
9
|
+
"reset": [boolean_validator],
|
10
|
+
}
|
@@ -1,8 +1,12 @@
|
|
1
1
|
from __future__ import annotations
|
2
|
+
|
2
3
|
from typing import Any
|
4
|
+
|
3
5
|
import ckan.plugins as p
|
4
6
|
import ckan.plugins.toolkit as tk
|
7
|
+
|
5
8
|
from ckanext.search_tweaks.interfaces import IQueryPopularity
|
9
|
+
|
6
10
|
from . import config, score
|
7
11
|
|
8
12
|
|
@@ -16,12 +20,16 @@ class QueryPopularityPlugin(p.SingletonPlugin):
|
|
16
20
|
|
17
21
|
def after_dataset_search(self, results: dict[str, Any], params: dict[str, Any]):
|
18
22
|
bp, view = tk.get_endpoint()
|
19
|
-
if
|
20
|
-
|
23
|
+
if (
|
24
|
+
bp
|
25
|
+
and view
|
26
|
+
and f"{bp}.{view}" in config.tracked_endpoints()
|
27
|
+
and not any(
|
21
28
|
plugin.skip_query_popularity(params)
|
22
29
|
for plugin in p.PluginImplementations(IQueryPopularity)
|
23
|
-
)
|
24
|
-
|
30
|
+
)
|
31
|
+
):
|
32
|
+
self.score.hit(params["q"].strip())
|
25
33
|
|
26
34
|
return results
|
27
35
|
|
@@ -40,8 +48,4 @@ class QueryPopularityPlugin(p.SingletonPlugin):
|
|
40
48
|
|
41
49
|
terms = config.ignored_terms()
|
42
50
|
|
43
|
-
for term in terms
|
44
|
-
if term in q:
|
45
|
-
return True
|
46
|
-
|
47
|
-
return False
|
51
|
+
return any(term in q for term in terms)
|
@@ -0,0 +1,46 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import logging
|
4
|
+
from typing import Any, Iterable
|
5
|
+
|
6
|
+
from ckanext.toolbelt.utils.tracking import DateTracker
|
7
|
+
|
8
|
+
from . import config
|
9
|
+
|
10
|
+
log = logging.getLogger(__name__)
|
11
|
+
connect_to_redis: Any
|
12
|
+
|
13
|
+
|
14
|
+
class Score:
|
15
|
+
def __init__(self):
|
16
|
+
self.track = DateTracker(
|
17
|
+
"search_tweaks:qp",
|
18
|
+
throttling_time=config.throttle(),
|
19
|
+
max_age=config.max_age(),
|
20
|
+
obsoletion_period=config.obsoletion_period(),
|
21
|
+
personalized=True,
|
22
|
+
)
|
23
|
+
|
24
|
+
def export(self):
|
25
|
+
return self.track.snapshot()
|
26
|
+
|
27
|
+
def restore(self, snapshot: Any):
|
28
|
+
return self.track.restore(snapshot)
|
29
|
+
|
30
|
+
def hit(self, q: str):
|
31
|
+
self.track.hit(q)
|
32
|
+
|
33
|
+
def refresh(self):
|
34
|
+
self.track.refresh()
|
35
|
+
|
36
|
+
def stats(self, num: int) -> Iterable[dict[str, str | float]]:
|
37
|
+
return self.track.most_common(num)
|
38
|
+
|
39
|
+
def drop(self, q: str):
|
40
|
+
self.track.drop(q)
|
41
|
+
|
42
|
+
def reset(self):
|
43
|
+
self.track.reset()
|
44
|
+
|
45
|
+
def ignore(self, q: str):
|
46
|
+
self.track.ignore(self.track.hash(q))
|
@@ -6,9 +6,10 @@ from typing import Any
|
|
6
6
|
import ckan.plugins as plugins
|
7
7
|
import ckan.plugins.toolkit as tk
|
8
8
|
|
9
|
-
from ckanext.search_tweaks import feature_disabled
|
10
9
|
from ckanext.search_tweaks.cli import attach_relevance_command
|
11
10
|
from ckanext.search_tweaks.interfaces import ISearchTweaks
|
11
|
+
from ckanext.search_tweaks.shared import feature_disabled
|
12
|
+
|
12
13
|
from . import QueryScore, cli, normalize_query, update_score_by_url
|
13
14
|
|
14
15
|
CONFIG_BOOST_STRING = "ckanext.search_tweaks.query_relevance.boost_function"
|
@@ -1,10 +1,11 @@
|
|
1
1
|
from __future__ import annotations
|
2
|
-
|
2
|
+
|
3
|
+
from abc import ABC, abstractmethod
|
3
4
|
from datetime import date, timedelta
|
4
|
-
from typing import Any, Iterable,
|
5
|
+
from typing import Any, Iterable, Tuple, cast
|
5
6
|
|
6
7
|
import ckan.plugins.toolkit as tk
|
7
|
-
from ckan.lib.redis import
|
8
|
+
from ckan.lib.redis import Redis, connect_to_redis
|
8
9
|
|
9
10
|
CONFIG_DAILY_AGE = "ckanext.search_tweaks.query_relevance.daily.age"
|
10
11
|
DEFAULT_DAILY_AGE = 90
|
@@ -36,13 +37,13 @@ class ScoreStorage(ABC):
|
|
36
37
|
...
|
37
38
|
|
38
39
|
@classmethod
|
39
|
-
@
|
40
|
+
@abstractmethod
|
40
41
|
def scan(cls, id_: str | None = None) -> Iterable[ScanItem]:
|
41
42
|
"""Get all the scores."""
|
42
43
|
...
|
43
44
|
|
44
45
|
@classmethod
|
45
|
-
@
|
46
|
+
@abstractmethod
|
46
47
|
def reset_storage(cls):
|
47
48
|
"""Remove everything from storage."""
|
48
49
|
...
|
@@ -53,6 +54,7 @@ class ScoreStorage(ABC):
|
|
53
54
|
|
54
55
|
def align(self) -> None:
|
55
56
|
"""Make some cleanup in order to maintain fast and correct value."""
|
57
|
+
return
|
56
58
|
|
57
59
|
|
58
60
|
class RedisScoreStorage(ScoreStorage):
|
@@ -80,8 +82,7 @@ class RedisScoreStorage(ScoreStorage):
|
|
80
82
|
conn.delete(key)
|
81
83
|
|
82
84
|
@abstractmethod
|
83
|
-
def _key(self) -> str:
|
84
|
-
...
|
85
|
+
def _key(self) -> str: ...
|
85
86
|
|
86
87
|
def reset(self):
|
87
88
|
self.conn.delete(self._key())
|
@@ -135,8 +136,7 @@ class DailyRedisScoreStorage(RedisScoreStorage):
|
|
135
136
|
def get(self) -> int:
|
136
137
|
key = self._key()
|
137
138
|
values = self.conn.zrange(key, 0, -1, withscores=True)
|
138
|
-
|
139
|
-
return total
|
139
|
+
return self._total(values)
|
140
140
|
|
141
141
|
@staticmethod
|
142
142
|
def _total(values: list[tuple[Any, Any]]) -> int:
|
@@ -24,7 +24,9 @@ def get_helpers():
|
|
24
24
|
|
25
25
|
|
26
26
|
def spellcheck_did_you_mean(
|
27
|
-
q: str,
|
27
|
+
q: str,
|
28
|
+
min_hits: int = 0,
|
29
|
+
max_suggestions: int = None,
|
28
30
|
) -> list[str]:
|
29
31
|
"""Return optimal query that can be used instead of the current one.
|
30
32
|
|
@@ -71,7 +73,11 @@ def spellcheck_did_you_mean(
|
|
71
73
|
|
72
74
|
# TODO: check min hits
|
73
75
|
new_q = " ".join(
|
74
|
-
[
|
76
|
+
[
|
77
|
+
spellcheck.suggestions[w][0]
|
78
|
+
for w in terms
|
79
|
+
if w in spellcheck.suggestions
|
80
|
+
],
|
75
81
|
)
|
76
82
|
if new_q:
|
77
83
|
collations.append(new_q)
|
@@ -44,8 +44,8 @@ class TestHelper:
|
|
44
44
|
Dataset(title="Do not touch me")
|
45
45
|
helper = tk.h.spellcheck_did_you_mean
|
46
46
|
rebuild_dictionary()
|
47
|
-
assert helper("pick thes") == ["pick
|
48
|
-
assert helper("do nat touc
|
47
|
+
assert helper("pick thes") == ["pick this"]
|
48
|
+
assert helper("do nat touc") == ["do not touch"]
|
49
49
|
|
50
50
|
assert helper("pic", 3) == [
|
51
51
|
"pick",
|
@@ -3,7 +3,6 @@ import pytest
|
|
3
3
|
import ckan.lib.search.query as query
|
4
4
|
import ckan.plugins as p
|
5
5
|
|
6
|
-
import ckanext.search_tweaks.plugin as plugin
|
7
6
|
import ckanext.search_tweaks.config as config
|
8
7
|
from ckanext.search_tweaks.config import CONFIG_PREFER_BOOST
|
9
8
|
|
@@ -76,7 +75,11 @@ class TestFuzzy:
|
|
76
75
|
@pytest.mark.ckan_config(config.CONFIG_FUZZY, True)
|
77
76
|
@pytest.mark.parametrize("distance", [-10, -1, 0])
|
78
77
|
def test_fuzzy_enabled_with_too_low_distance(
|
79
|
-
self,
|
78
|
+
self,
|
79
|
+
search,
|
80
|
+
distance,
|
81
|
+
ckan_config,
|
82
|
+
monkeypatch,
|
80
83
|
):
|
81
84
|
monkeypatch.setitem(ckan_config, config.CONFIG_FUZZY_DISTANCE, distance)
|
82
85
|
assert search(q="")["q"] == "*:*"
|
@@ -89,7 +92,11 @@ class TestFuzzy:
|
|
89
92
|
@pytest.mark.ckan_config(config.CONFIG_FUZZY, True)
|
90
93
|
@pytest.mark.parametrize("distance", [3, 20, 111])
|
91
94
|
def test_fuzzy_enabled_with_too_high_distance(
|
92
|
-
self,
|
95
|
+
self,
|
96
|
+
search,
|
97
|
+
distance,
|
98
|
+
ckan_config,
|
99
|
+
monkeypatch,
|
93
100
|
):
|
94
101
|
monkeypatch.setitem(ckan_config, config.CONFIG_FUZZY_DISTANCE, distance)
|
95
102
|
assert search()["q"] == "*:*"
|
@@ -101,7 +108,11 @@ class TestFuzzy:
|
|
101
108
|
@pytest.mark.ckan_config(config.CONFIG_FUZZY, True)
|
102
109
|
@pytest.mark.parametrize("distance", [1, 2])
|
103
110
|
def test_fuzzy_keep_original_query(
|
104
|
-
self,
|
111
|
+
self,
|
112
|
+
search,
|
113
|
+
distance,
|
114
|
+
ckan_config,
|
115
|
+
monkeypatch,
|
105
116
|
):
|
106
117
|
monkeypatch.setitem(ckan_config, config.CONFIG_FUZZY_DISTANCE, distance)
|
107
118
|
assert search()["q"] == "*:*"
|
@@ -1,10 +1,10 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: ckanext-search-tweaks
|
3
|
-
Version: 0.6.
|
4
|
-
|
5
|
-
|
6
|
-
Author-email: sergey.motornyuk@linkdigital.com.au
|
3
|
+
Version: 0.6.1.post1
|
4
|
+
Author-email: DataShades <datashades@linkdigital.com.au>, Sergey Motornyuk <sergey.motornyuk@linkdigital.com.au>
|
5
|
+
Maintainer-email: DataShades <datashades@linkdigital.com.au>
|
7
6
|
License: AGPL
|
7
|
+
Project-URL: Homepage, https://github.com/DataShades/ckanext-search-tweaks
|
8
8
|
Keywords: CKAN
|
9
9
|
Classifier: Development Status :: 4 - Beta
|
10
10
|
Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
|
@@ -15,6 +15,7 @@ Description-Content-Type: text/markdown
|
|
15
15
|
License-File: LICENSE
|
16
16
|
Requires-Dist: freezegun
|
17
17
|
Requires-Dist: typing_extensions>=4.0.0
|
18
|
+
Requires-Dist: ckanext-toolbelt>=0.4.11
|
18
19
|
Provides-Extra: advanced-search
|
19
20
|
|
20
21
|
[](https://github.com/DataShades/ckanext-search-tweaks/actions)
|
@@ -31,6 +31,7 @@ ckanext/search_tweaks/query_popularity/score.py
|
|
31
31
|
ckanext/search_tweaks/query_popularity/logic/__init__.py
|
32
32
|
ckanext/search_tweaks/query_popularity/logic/action.py
|
33
33
|
ckanext/search_tweaks/query_popularity/logic/auth.py
|
34
|
+
ckanext/search_tweaks/query_popularity/logic/schema.py
|
34
35
|
ckanext/search_tweaks/query_relevance/__init__.py
|
35
36
|
ckanext/search_tweaks/query_relevance/cli.py
|
36
37
|
ckanext/search_tweaks/query_relevance/plugin.py
|
@@ -54,6 +55,5 @@ ckanext_search_tweaks.egg-info/PKG-INFO
|
|
54
55
|
ckanext_search_tweaks.egg-info/SOURCES.txt
|
55
56
|
ckanext_search_tweaks.egg-info/dependency_links.txt
|
56
57
|
ckanext_search_tweaks.egg-info/entry_points.txt
|
57
|
-
ckanext_search_tweaks.egg-info/namespace_packages.txt
|
58
58
|
ckanext_search_tweaks.egg-info/requires.txt
|
59
59
|
ckanext_search_tweaks.egg-info/top_level.txt
|
@@ -1,9 +1,57 @@
|
|
1
|
+
[build-system]
|
2
|
+
requires = [ "setuptools",]
|
3
|
+
build-backend = "setuptools.build_meta"
|
4
|
+
|
5
|
+
[project]
|
6
|
+
name = "ckanext-search-tweaks"
|
7
|
+
version = "0.6.1.post1"
|
8
|
+
description = ""
|
9
|
+
classifiers = [ "Development Status :: 4 - Beta", "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10",]
|
10
|
+
keywords = [ "CKAN",]
|
11
|
+
dependencies = [ "freezegun", "typing_extensions>=4.0.0", "ckanext-toolbelt>=0.4.11",]
|
12
|
+
authors = [
|
13
|
+
{name = "DataShades", email = "datashades@linkdigital.com.au"},
|
14
|
+
{name = "Sergey Motornyuk", email = "sergey.motornyuk@linkdigital.com.au"},
|
15
|
+
]
|
16
|
+
maintainers = [
|
17
|
+
{name = "DataShades", email = "datashades@linkdigital.com.au"},
|
18
|
+
]
|
19
|
+
|
20
|
+
[project.readme]
|
21
|
+
file = "README.md"
|
22
|
+
content-type = "text/markdown"
|
23
|
+
|
24
|
+
[project.license]
|
25
|
+
text = "AGPL"
|
26
|
+
|
27
|
+
[project.urls]
|
28
|
+
Homepage = "https://github.com/DataShades/ckanext-search-tweaks"
|
29
|
+
|
30
|
+
[project.optional-dependencies]
|
31
|
+
advanced-search = []
|
32
|
+
|
33
|
+
[project.entry-points."ckan.plugins"]
|
34
|
+
search_tweaks = "ckanext.search_tweaks.plugin:SearchTweaksPlugin"
|
35
|
+
search_tweaks_query_relevance = "ckanext.search_tweaks.query_relevance.plugin:QueryRelevancePlugin"
|
36
|
+
search_tweaks_field_relevance = "ckanext.search_tweaks.field_relevance.plugin:FieldRelevancePlugin"
|
37
|
+
search_tweaks_spellcheck = "ckanext.search_tweaks.spellcheck.plugin:SpellcheckPlugin"
|
38
|
+
search_tweaks_advanced_search = "ckanext.search_tweaks.advanced_search.plugin:AdvancedSearchPlugin"
|
39
|
+
search_tweaks_query_popularity = "ckanext.search_tweaks.query_popularity.plugin:QueryPopularityPlugin"
|
40
|
+
|
41
|
+
[project.entry-points."babel.extractors"]
|
42
|
+
ckan = "ckan.lib.extract:extract_ckan"
|
43
|
+
|
44
|
+
[tool.setuptools.packages]
|
45
|
+
find = {}
|
46
|
+
|
1
47
|
[tool.black]
|
2
48
|
# line-length = 88
|
3
49
|
# preview = true
|
4
50
|
|
5
51
|
[tool.ruff]
|
6
52
|
target-version = "py38"
|
53
|
+
|
54
|
+
[tool.ruff.lint]
|
7
55
|
select = [
|
8
56
|
"B", # likely bugs and design problems
|
9
57
|
"BLE", # do not catch blind exception
|
@@ -34,11 +82,8 @@ ignore = [
|
|
34
82
|
# "PLC1901", # simplify comparison to empty string: violated by SQLAlchemy filters
|
35
83
|
]
|
36
84
|
|
37
|
-
[tool.ruff.per-file-ignores]
|
38
|
-
"ckanext/
|
39
|
-
"S", # security testing
|
40
|
-
"PLR2004" # magic value used in comparison
|
41
|
-
]
|
85
|
+
[tool.ruff.lint.per-file-ignores]
|
86
|
+
"ckanext/search_tweaks/tests*" = ["S", "PL"]
|
42
87
|
|
43
88
|
[tool.isort]
|
44
89
|
known_ckan = "ckan"
|
File without changes
|
@@ -0,0 +1,26 @@
|
|
1
|
+
[extract_messages]
|
2
|
+
keywords = translate isPlural
|
3
|
+
add_comments = TRANSLATORS:
|
4
|
+
output_file = ckanext/search_tweaks/i18n/ckanext-search_tweaks.pot
|
5
|
+
width = 80
|
6
|
+
|
7
|
+
[init_catalog]
|
8
|
+
domain = ckanext-search_tweaks
|
9
|
+
input_file = ckanext/search_tweaks/i18n/ckanext-search_tweaks.pot
|
10
|
+
output_dir = ckanext/search_tweaks/i18n
|
11
|
+
|
12
|
+
[update_catalog]
|
13
|
+
domain = ckanext-search_tweaks
|
14
|
+
input_file = ckanext/search_tweaks/i18n/ckanext-search_tweaks.pot
|
15
|
+
output_dir = ckanext/search_tweaks/i18n
|
16
|
+
previous = true
|
17
|
+
|
18
|
+
[compile_catalog]
|
19
|
+
domain = ckanext-search_tweaks
|
20
|
+
directory = ckanext/search_tweaks/i18n
|
21
|
+
statistics = true
|
22
|
+
|
23
|
+
[egg_info]
|
24
|
+
tag_build =
|
25
|
+
tag_date = 0
|
26
|
+
|
@@ -1,165 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
from collections import defaultdict
|
3
|
-
from datetime import datetime, timedelta
|
4
|
-
import logging
|
5
|
-
from hashlib import md5
|
6
|
-
from typing import Any, Iterable, cast
|
7
|
-
from operator import itemgetter
|
8
|
-
from ckan.lib.redis import connect_to_redis
|
9
|
-
import ckan.plugins.toolkit as tk
|
10
|
-
from redis import Redis
|
11
|
-
from . import config
|
12
|
-
|
13
|
-
log = logging.getLogger(__name__)
|
14
|
-
connect_to_redis: Any
|
15
|
-
|
16
|
-
|
17
|
-
class Score:
|
18
|
-
redis: Redis[bytes]
|
19
|
-
date_format = "%Y-%m-%d %H-%M"
|
20
|
-
|
21
|
-
def __init__(self):
|
22
|
-
self.redis = connect_to_redis()
|
23
|
-
|
24
|
-
site = tk.config["ckan.site_id"]
|
25
|
-
self.prefix = f"{site}:search_tweaks:qp"
|
26
|
-
|
27
|
-
def export(self):
|
28
|
-
data: dict[bytes, dict[str, Any]] = {
|
29
|
-
hash: {"query": query, "records": []}
|
30
|
-
for hash, query in self.redis.hgetall(self.trans_key()).items()
|
31
|
-
}
|
32
|
-
for k, v in self.redis.hscan_iter(self.distribution_key()):
|
33
|
-
date_str, q_hash = k.split(b"/", 1)
|
34
|
-
try:
|
35
|
-
date = datetime.strptime(date_str.decode(), self.date_format)
|
36
|
-
except ValueError:
|
37
|
-
continue
|
38
|
-
|
39
|
-
data[q_hash]["records"].append({"date": date, "count": int(v)})
|
40
|
-
|
41
|
-
return list(data.values())
|
42
|
-
|
43
|
-
def save(self, q: str):
|
44
|
-
q = q.strip()
|
45
|
-
q_hash = self.hash(q)
|
46
|
-
|
47
|
-
if self.is_ignored(q_hash):
|
48
|
-
return
|
49
|
-
|
50
|
-
if self.is_throttling(q_hash):
|
51
|
-
return
|
52
|
-
|
53
|
-
self.redis.hset(self.trans_key(), q_hash, q)
|
54
|
-
|
55
|
-
date_stem = self.format_date_stem(self.now())
|
56
|
-
|
57
|
-
self.redis.hincrby(self.distribution_key(), f"{date_stem}/{q_hash}", 1)
|
58
|
-
|
59
|
-
def drop(self, q: str):
|
60
|
-
q_hash = self.hash(q)
|
61
|
-
dk = self.distribution_key()
|
62
|
-
|
63
|
-
series = self.redis.hscan_iter(dk, f"*/{q_hash}")
|
64
|
-
keys = list(map(itemgetter(0), series))
|
65
|
-
if keys:
|
66
|
-
self.redis.hdel(dk, *keys)
|
67
|
-
|
68
|
-
self.redis.hdel(self.trans_key(), q_hash)
|
69
|
-
self.redis.zrem(self.score_key(), q_hash)
|
70
|
-
|
71
|
-
def is_throttling(self, q_hash: str):
|
72
|
-
user = tk.current_user.name
|
73
|
-
|
74
|
-
throttle_key = f"{self.prefix}:throttle:{user}:{q_hash}"
|
75
|
-
if self.redis.exists(throttle_key):
|
76
|
-
return True
|
77
|
-
|
78
|
-
self.redis.set(throttle_key, 1, ex=config.throttle())
|
79
|
-
return False
|
80
|
-
|
81
|
-
def reset(self):
|
82
|
-
keys = self.redis.keys(f"{self.prefix}:*")
|
83
|
-
if keys:
|
84
|
-
self.redis.delete(*keys)
|
85
|
-
|
86
|
-
def refresh(self):
|
87
|
-
max_age = timedelta(seconds=config.max_age())
|
88
|
-
dk = self.distribution_key()
|
89
|
-
sk = self.score_key()
|
90
|
-
|
91
|
-
expired_dist: set[bytes] = set()
|
92
|
-
distribution = cast(
|
93
|
-
"Iterable[tuple[bytes, bytes]]",
|
94
|
-
self.redis.hscan_iter(dk),
|
95
|
-
)
|
96
|
-
|
97
|
-
scores: dict[bytes, float] = defaultdict(float)
|
98
|
-
|
99
|
-
for k, v in distribution:
|
100
|
-
date_str, q_hash = k.split(b"/", 1)
|
101
|
-
try:
|
102
|
-
date = datetime.strptime(date_str.decode(), self.date_format)
|
103
|
-
except ValueError:
|
104
|
-
log.error("Remove invalid key %s", k)
|
105
|
-
expired_dist.add(k)
|
106
|
-
continue
|
107
|
-
|
108
|
-
age = self.now() - date
|
109
|
-
|
110
|
-
if age > max_age:
|
111
|
-
expired_dist.add(k)
|
112
|
-
continue
|
113
|
-
|
114
|
-
scores[q_hash] += int(v) / (age.seconds // config.obsoletion_period() + 1)
|
115
|
-
|
116
|
-
if expired_dist:
|
117
|
-
self.redis.hdel(dk, *expired_dist)
|
118
|
-
|
119
|
-
expired_scores: set[bytes] = set()
|
120
|
-
for k, v in self.redis.zscan_iter(sk):
|
121
|
-
if k not in scores:
|
122
|
-
expired_scores.add(k)
|
123
|
-
continue
|
124
|
-
if scores:
|
125
|
-
self.redis.zadd(sk, cast(Any, scores))
|
126
|
-
|
127
|
-
if expired_scores:
|
128
|
-
self.redis.zrem(sk, *expired_scores)
|
129
|
-
self.redis.hdel(self.trans_key(), *expired_scores)
|
130
|
-
|
131
|
-
def hash(self, q: str):
|
132
|
-
return md5(q.encode()).hexdigest()
|
133
|
-
|
134
|
-
def is_ignored(self, q_hash: str):
|
135
|
-
return self.redis.sismember(self.ignore_key(), q_hash)
|
136
|
-
|
137
|
-
def ignore(self, q: str):
|
138
|
-
return self.redis.sadd(self.ignore_key(), self.hash(q))
|
139
|
-
|
140
|
-
def now(self):
|
141
|
-
return datetime.utcnow()
|
142
|
-
|
143
|
-
def score_key(self):
|
144
|
-
return f"{self.prefix}:score"
|
145
|
-
|
146
|
-
def trans_key(self):
|
147
|
-
return f"{self.prefix}:trans"
|
148
|
-
|
149
|
-
def ignore_key(self):
|
150
|
-
return f"{self.prefix}:ignore"
|
151
|
-
|
152
|
-
def distribution_key(self):
|
153
|
-
return f"{self.prefix}:distribution"
|
154
|
-
|
155
|
-
def format_date_stem(self, date: datetime):
|
156
|
-
return date.strftime(self.date_format)
|
157
|
-
|
158
|
-
def stats(self, num: int) -> Iterable[dict[str, Any]]:
|
159
|
-
scores: list[tuple[bytes, float]] = self.redis.zrange(
|
160
|
-
self.score_key(), 0, num - 1, desc=True, withscores=True
|
161
|
-
)
|
162
|
-
trans_key = self.trans_key()
|
163
|
-
|
164
|
-
for k, v in scores:
|
165
|
-
yield {"query": self.redis.hget(trans_key, k), "score": v}
|
@@ -1 +0,0 @@
|
|
1
|
-
ckanext
|
@@ -1,66 +0,0 @@
|
|
1
|
-
[metadata]
|
2
|
-
name = ckanext-search-tweaks
|
3
|
-
version = 0.6.0a1
|
4
|
-
description =
|
5
|
-
long_description = file: README.md
|
6
|
-
long_description_content_type = text/markdown
|
7
|
-
url = https://github.com/DataShades/ckanext-search-tweaks
|
8
|
-
author = Sergey Motornyuk
|
9
|
-
author_email = sergey.motornyuk@linkdigital.com.au
|
10
|
-
license = AGPL
|
11
|
-
classifiers =
|
12
|
-
Development Status :: 4 - Beta
|
13
|
-
License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
|
14
|
-
Programming Language :: Python :: 3.8
|
15
|
-
Programming Language :: Python :: 3.9
|
16
|
-
Programming Language :: Python :: 3.10
|
17
|
-
keywords = CKAN
|
18
|
-
|
19
|
-
[options]
|
20
|
-
packages = find:
|
21
|
-
namespace_packages = ckanext
|
22
|
-
install_requires =
|
23
|
-
freezegun
|
24
|
-
typing_extensions>=4.0.0
|
25
|
-
include_package_data = True
|
26
|
-
|
27
|
-
[options.entry_points]
|
28
|
-
ckan.plugins =
|
29
|
-
search_tweaks = ckanext.search_tweaks.plugin:SearchTweaksPlugin
|
30
|
-
search_tweaks_query_relevance = ckanext.search_tweaks.query_relevance.plugin:QueryRelevancePlugin
|
31
|
-
search_tweaks_field_relevance = ckanext.search_tweaks.field_relevance.plugin:FieldRelevancePlugin
|
32
|
-
search_tweaks_spellcheck = ckanext.search_tweaks.spellcheck.plugin:SpellcheckPlugin
|
33
|
-
search_tweaks_advanced_search = ckanext.search_tweaks.advanced_search.plugin:AdvancedSearchPlugin
|
34
|
-
search_tweaks_query_popularity = ckanext.search_tweaks.query_popularity.plugin:QueryPopularityPlugin
|
35
|
-
babel.extractors =
|
36
|
-
ckan = ckan.lib.extract:extract_ckan
|
37
|
-
|
38
|
-
[options.extras_require]
|
39
|
-
advanced-search =
|
40
|
-
|
41
|
-
[extract_messages]
|
42
|
-
keywords = translate isPlural
|
43
|
-
add_comments = TRANSLATORS:
|
44
|
-
output_file = ckanext/search_tweaks/i18n/ckanext-search_tweaks.pot
|
45
|
-
width = 80
|
46
|
-
|
47
|
-
[init_catalog]
|
48
|
-
domain = ckanext-search_tweaks
|
49
|
-
input_file = ckanext/search_tweaks/i18n/ckanext-search_tweaks.pot
|
50
|
-
output_dir = ckanext/search_tweaks/i18n
|
51
|
-
|
52
|
-
[update_catalog]
|
53
|
-
domain = ckanext-search_tweaks
|
54
|
-
input_file = ckanext/search_tweaks/i18n/ckanext-search_tweaks.pot
|
55
|
-
output_dir = ckanext/search_tweaks/i18n
|
56
|
-
previous = true
|
57
|
-
|
58
|
-
[compile_catalog]
|
59
|
-
domain = ckanext-search_tweaks
|
60
|
-
directory = ckanext/search_tweaks/i18n
|
61
|
-
statistics = true
|
62
|
-
|
63
|
-
[egg_info]
|
64
|
-
tag_build =
|
65
|
-
tag_date = 0
|
66
|
-
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext/search_tweaks/cli.py
RENAMED
File without changes
|
{ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext/search_tweaks/config.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{ckanext-search-tweaks-0.6.0a1 → ckanext_search_tweaks-0.6.1.post1}/ckanext/search_tweaks/shared.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|