We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Consider using ahocorasick-rs instead of pyahocorasick
### Describe the problem

https://pypi.org/project/ahocorasick-rs/ seems faster alternative to pyahocorasick.

### Describe the solution you'd like

It would be useful to benchmark it in Weblate use-case and switch to it in case it outperforms pyahocorasick.

### Describe alternatives you've considered

_No response_

### Screenshots

_No response_

### Additional context

> That being said, I've seen ahocorasick_rs run 1.5× to 7× as fast as pyahocorasick, depending on the options used.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `weblate/glossary/models.py`
Content:
```
1 # Copyright © Michal Čihař <michal@weblate.org>
2 #
3 # SPDX-License-Identifier: GPL-3.0-or-later
4 
5 import re
6 from itertools import chain
7 
8 import ahocorasick
9 import sentry_sdk
10 from django.db.models import Q
11 from django.db.models.functions import Lower
12 
13 from weblate.trans.models.unit import Unit
14 from weblate.trans.util import PLURAL_SEPARATOR
15 from weblate.utils.db import re_escape, using_postgresql
16 from weblate.utils.state import STATE_TRANSLATED
17 
18 SPLIT_RE = re.compile(r"[\s,.:!?]+", re.UNICODE)
19 NON_WORD_RE = re.compile(r"\W", re.UNICODE)
20 
21 
22 def get_glossary_sources(component):
23     # Fetch list of terms defined in a translation
24     return list(
25         set(
26             component.source_translation.unit_set.filter(
27                 state__gte=STATE_TRANSLATED
28             ).values_list(Lower("source"), flat=True)
29         )
30     )
31 
32 
33 def get_glossary_automaton(project):
34     with sentry_sdk.start_span(op="glossary.automaton", description=project.slug):
35         # Chain terms
36         terms = set(
37             chain.from_iterable(
38                 glossary.glossary_sources for glossary in project.glossaries
39             )
40         )
41         # Build automaton for efficient Aho-Corasick search
42         automaton = ahocorasick.Automaton()
43         for term in terms:
44             automaton.add_word(term, term)
45         automaton.make_automaton()
46         return automaton
47 
48 
49 def get_glossary_terms(unit):
50     """Return list of term pairs for an unit."""
51     if unit.glossary_terms is not None:
52         return unit.glossary_terms
53     translation = unit.translation
54     language = translation.language
55     component = translation.component
56     project = component.project
57     source_language = component.source_language
58 
59     units = (
60         Unit.objects.prefetch()
61         .select_related("source_unit")
62         .order_by("translation__component__priority", Lower("source"))
63     )
64     if language == source_language:
65         return units.none()
66 
67     # Build complete source for matching
68     parts = []
69     for text in unit.get_source_plurals():
70         text = text.lower().strip()
71         if text:
72             parts.append(text)
73     source = PLURAL_SEPARATOR.join(parts)
74 
75     uses_ngram = source_language.uses_ngram()
76 
77     terms = set()
78     automaton = project.glossary_automaton
79     if automaton.kind == ahocorasick.AHOCORASICK:
80         # Extract terms present in the source
81         with sentry_sdk.start_span(op="glossary.match", description=project.slug):
82             for end, term in automaton.iter(source):
83                 if uses_ngram or (
84                     (end + 1 == len(term) or NON_WORD_RE.match(source[end - len(term)]))
85                     and (end + 1 == len(source) or NON_WORD_RE.match(source[end + 1]))
86                 ):
87                     terms.add(term)
88 
89     if using_postgresql():
90         match = r"^({})$".format("|".join(re_escape(term) for term in terms))
91         # Use regex as that is utilizing pg_trgm index
92         query = Q(source__iregex=match) | Q(variant__unit__source__iregex=match)
93     else:
94         # With MySQL we utilize it does case insensitive lookup
95         query = Q(source__in=terms) | Q(variant__unit__source__in=terms)
96 
97     units = units.filter(
98         query,
99         translation__component__in=project.glossaries,
100         translation__component__source_language=source_language,
101         translation__language=language,
102     ).distinct()
103 
104     # Store in a unit cache
105     unit.glossary_terms = units
106 
107     return units
108
```

Path: `weblate/utils/requirements.py`
Content:
```
1 # Copyright © Michal Čihař <michal@weblate.org>
2 #
3 # SPDX-License-Identifier: GPL-3.0-or-later
4 
5 import sys
6 from importlib.metadata import PackageNotFoundError, metadata
7 
8 from django.conf import settings
9 from django.core.cache import cache
10 from django.core.exceptions import ImproperlyConfigured
11 from django.db import connection
12 
13 import weblate.utils.version
14 from weblate.utils.db import using_postgresql
15 from weblate.utils.errors import report_error
16 from weblate.vcs.git import GitRepository, GitWithGerritRepository, SubversionRepository
17 from weblate.vcs.mercurial import HgRepository
18 
19 REQUIRES = [
20     "Django",
21     "siphashc",
22     "translate-toolkit",
23     "lxml",
24     "Pillow",
25     "nh3",
26     "python-dateutil",
27     "social-auth-core",
28     "social-auth-app-django",
29     "django-crispy-forms",
30     "oauthlib",
31     "django-compressor",
32     "djangorestframework",
33     "django-filter",
34     "django-appconf",
35     "user-agents",
36     "filelock",
37     "rapidfuzz",
38     "openpyxl",
39     "celery",
40     "django-celery-beat",
41     "kombu",
42     "translation-finder",
43     "weblate-language-data",
44     "html2text",
45     "pycairo",
46     "pygobject",
47     "diff-match-patch",
48     "requests",
49     "django-redis",
50     "hiredis",
51     "sentry_sdk",
52     "Cython",
53     "misaka",
54     "GitPython",
55     "borgbackup",
56     "pyparsing",
57     "pyahocorasick",
58     "python-redis-lock",
59     "charset-normalizer",
60 ]
61 
62 OPTIONAL = [
63     "psycopg2",
64     "psycopg2-binary",
65     "phply",
66     "ruamel.yaml",
67     "tesserocr",
68     "akismet",
69     "boto3",
70     "zeep",
71     "aeidon",
72     "iniparse",
73     "mysqlclient",
74 ]
75 
76 
77 def get_version_module(name, optional=False):
78     """
79     Return module object.
80 
81     On error raises verbose exception with name and URL.
82     """
83     try:
84         package = metadata(name)
85     except PackageNotFoundError as exc:
86         if optional:
87             return None
88         raise ImproperlyConfigured(
89             f"Missing dependency {name}, please install using: pip install {name}"
90         ) from exc
91     url = package.get("Home-page")
92     if url is None:
93         for project_url in package.get_all("Project-URL"):
94             name, current_url = project_url.split(",", 1)
95             if name.lower().strip() == "homepage":
96                 url = current_url.strip()
97                 break
98     if url is None:
99         url = f"https://pypi.org/project/{name}/"
100     return (
101         package.get("Name"),
102         url,
103         package.get("Version"),
104     )
105 
106 
107 def get_optional_versions():
108     """Return versions of optional modules."""
109     result = []
110 
111     for name in OPTIONAL:
112         module = get_version_module(name, True)
113         if module is not None:
114             result.append(module)
115 
116     if HgRepository.is_supported():
117         result.append(
118             ("Mercurial", "https://www.mercurial-scm.org/", HgRepository.get_version())
119         )
120 
121     if SubversionRepository.is_supported():
122         result.append(
123             (
124                 "git-svn",
125                 "https://git-scm.com/docs/git-svn",
126                 SubversionRepository.get_version(),
127             )
128         )
129 
130     if GitWithGerritRepository.is_supported():
131         result.append(
132             (
133                 "git-review",
134                 "https://pypi.org/project/git-review/",
135                 GitWithGerritRepository.get_version(),
136             )
137         )
138 
139     return result
140 
141 
142 def get_versions():
143     """Return list of used versions."""
144     result = [get_version_module(name) for name in REQUIRES]
145 
146     result.append(("Python", "https://www.python.org/", sys.version.split()[0]))
147 
148     try:
149         result.append(("Git", "https://git-scm.com/", GitRepository.get_version()))
150     except OSError as exc:
151         raise ImproperlyConfigured("Could not run git, please install it.") from exc
152 
153     return result
154 
155 
156 def get_db_version():
157     if using_postgresql():
158         try:
159             with connection.cursor() as cursor:
160                 cursor.execute("SHOW server_version")
161                 version = cursor.fetchone()
162         except RuntimeError:
163             report_error(cause="PostgreSQL version check")
164             return None
165 
166         return (
167             "PostgreSQL server",
168             "https://www.postgresql.org/",
169             version[0].split(" ")[0],
170         )
171     try:
172         with connection.cursor() as cursor:
173             version = cursor.connection.get_server_info()
174     except RuntimeError:
175         report_error(cause="MySQL version check")
176         return None
177     return (
178         f"{connection.display_name} sever",
179         "https://mariadb.org/"
180         if connection.mysql_is_mariadb
181         else "https://www.mysql.com/",
182         version.split("-", 1)[0],
183     )
184 
185 
186 def get_cache_version():
187     if settings.CACHES["default"]["BACKEND"] == "django_redis.cache.RedisCache":
188         try:
189             version = cache.client.get_client().info()["redis_version"]
190         except RuntimeError:
191             report_error(cause="Redis version check")
192             return None
193 
194         return ("Redis server", "https://redis.io/", version)
195 
196     return None
197 
198 
199 def get_db_cache_version():
200     """Returns the list of all the Database and Cache version."""
201     result = []
202     cache_version = get_cache_version()
203     if cache_version:
204         result.append(cache_version)
205     db_version = get_db_version()
206     if db_version:
207         result.append(db_version)
208     return result
209 
210 
211 def get_versions_list():
212     """Return list with version information summary."""
213     return [
214         ("Weblate", "https://weblate.org/", weblate.utils.version.GIT_VERSION),
215         *get_versions(),
216         *get_optional_versions(),
217         *get_db_cache_version(),
218     ]
219
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/weblate/glossary/models.py b/weblate/glossary/models.py
--- a/weblate/glossary/models.py
+++ b/weblate/glossary/models.py
@@ -5,7 +5,7 @@
 import re
 from itertools import chain
 
-import ahocorasick
+import ahocorasick_rs
 import sentry_sdk
 from django.db.models import Q
 from django.db.models.functions import Lower
@@ -39,11 +39,11 @@
             )
         )
         # Build automaton for efficient Aho-Corasick search
-        automaton = ahocorasick.Automaton()
-        for term in terms:
-            automaton.add_word(term, term)
-        automaton.make_automaton()
-        return automaton
+        return ahocorasick_rs.AhoCorasick(
+            terms,
+            implementation=ahocorasick_rs.Implementation.ContiguousNFA,
+            store_patterns=False,
+        )
 
 
 def get_glossary_terms(unit):
@@ -76,15 +76,16 @@
 
     terms = set()
     automaton = project.glossary_automaton
-    if automaton.kind == ahocorasick.AHOCORASICK:
-        # Extract terms present in the source
-        with sentry_sdk.start_span(op="glossary.match", description=project.slug):
-            for end, term in automaton.iter(source):
-                if uses_ngram or (
-                    (end + 1 == len(term) or NON_WORD_RE.match(source[end - len(term)]))
-                    and (end + 1 == len(source) or NON_WORD_RE.match(source[end + 1]))
-                ):
-                    terms.add(term)
+    # Extract terms present in the source
+    with sentry_sdk.start_span(op="glossary.match", description=project.slug):
+        for _termno, start, end in automaton.find_matches_as_indexes(
+            source, overlapping=True
+        ):
+            if uses_ngram or (
+                (start == 0 or NON_WORD_RE.match(source[start - 1]))
+                and (end >= len(source) or NON_WORD_RE.match(source[end]))
+            ):
+                terms.add(source[start:end])
 
     if using_postgresql():
         match = r"^({})$".format("|".join(re_escape(term) for term in terms))
diff --git a/weblate/utils/requirements.py b/weblate/utils/requirements.py
--- a/weblate/utils/requirements.py
+++ b/weblate/utils/requirements.py
@@ -54,7 +54,7 @@
     "GitPython",
     "borgbackup",
     "pyparsing",
-    "pyahocorasick",
+    "ahocorasick_rs",
     "python-redis-lock",
     "charset-normalizer",
 ]

{"golden_diff": "diff --git a/weblate/glossary/models.py b/weblate/glossary/models.py\n--- a/weblate/glossary/models.py\n+++ b/weblate/glossary/models.py\n@@ -5,7 +5,7 @@\n import re\n from itertools import chain\n \n-import ahocorasick\n+import ahocorasick_rs\n import sentry_sdk\n from django.db.models import Q\n from django.db.models.functions import Lower\n@@ -39,11 +39,11 @@\n             )\n         )\n         # Build automaton for efficient Aho-Corasick search\n-        automaton = ahocorasick.Automaton()\n-        for term in terms:\n-            automaton.add_word(term, term)\n-        automaton.make_automaton()\n-        return automaton\n+        return ahocorasick_rs.AhoCorasick(\n+            terms,\n+            implementation=ahocorasick_rs.Implementation.ContiguousNFA,\n+            store_patterns=False,\n+        )\n \n \n def get_glossary_terms(unit):\n@@ -76,15 +76,16 @@\n \n     terms = set()\n     automaton = project.glossary_automaton\n-    if automaton.kind == ahocorasick.AHOCORASICK:\n-        # Extract terms present in the source\n-        with sentry_sdk.start_span(op=\"glossary.match\", description=project.slug):\n-            for end, term in automaton.iter(source):\n-                if uses_ngram or (\n-                    (end + 1 == len(term) or NON_WORD_RE.match(source[end - len(term)]))\n-                    and (end + 1 == len(source) or NON_WORD_RE.match(source[end + 1]))\n-                ):\n-                    terms.add(term)\n+    # Extract terms present in the source\n+    with sentry_sdk.start_span(op=\"glossary.match\", description=project.slug):\n+        for _termno, start, end in automaton.find_matches_as_indexes(\n+            source, overlapping=True\n+        ):\n+            if uses_ngram or (\n+                (start == 0 or NON_WORD_RE.match(source[start - 1]))\n+                and (end >= len(source) or NON_WORD_RE.match(source[end]))\n+            ):\n+                terms.add(source[start:end])\n \n     if using_postgresql():\n         match = r\"^({})$\".format(\"|\".join(re_escape(term) for term in terms))\ndiff --git a/weblate/utils/requirements.py b/weblate/utils/requirements.py\n--- a/weblate/utils/requirements.py\n+++ b/weblate/utils/requirements.py\n@@ -54,7 +54,7 @@\n     \"GitPython\",\n     \"borgbackup\",\n     \"pyparsing\",\n-    \"pyahocorasick\",\n+    \"ahocorasick_rs\",\n     \"python-redis-lock\",\n     \"charset-normalizer\",\n ]\n", "issue": "Consider using ahocorasick-rs instead of pyahocorasick\n### Describe the problem\n\nhttps://pypi.org/project/ahocorasick-rs/ seems faster alternative to pyahocorasick.\n\n### Describe the solution you'd like\n\nIt would be useful to benchmark it in Weblate use-case and switch to it in case it outperforms pyahocorasick.\n\n### Describe alternatives you've considered\n\n_No response_\n\n### Screenshots\n\n_No response_\n\n### Additional context\n\n> That being said, I've seen ahocorasick_rs run 1.5\u00d7 to 7\u00d7 as fast as pyahocorasick, depending on the options used.\n", "before_files": [{"content": "# Copyright \u00a9 Michal \u010ciha\u0159 <michal@weblate.org>\n#\n# SPDX-License-Identifier: GPL-3.0-or-later\n\nimport re\nfrom itertools import chain\n\nimport ahocorasick\nimport sentry_sdk\nfrom django.db.models import Q\nfrom django.db.models.functions import Lower\n\nfrom weblate.trans.models.unit import Unit\nfrom weblate.trans.util import PLURAL_SEPARATOR\nfrom weblate.utils.db import re_escape, using_postgresql\nfrom weblate.utils.state import STATE_TRANSLATED\n\nSPLIT_RE = re.compile(r\"[\\s,.:!?]+\", re.UNICODE)\nNON_WORD_RE = re.compile(r\"\\W\", re.UNICODE)\n\n\ndef get_glossary_sources(component):\n    # Fetch list of terms defined in a translation\n    return list(\n        set(\n            component.source_translation.unit_set.filter(\n                state__gte=STATE_TRANSLATED\n            ).values_list(Lower(\"source\"), flat=True)\n        )\n    )\n\n\ndef get_glossary_automaton(project):\n    with sentry_sdk.start_span(op=\"glossary.automaton\", description=project.slug):\n        # Chain terms\n        terms = set(\n            chain.from_iterable(\n                glossary.glossary_sources for glossary in project.glossaries\n            )\n        )\n        # Build automaton for efficient Aho-Corasick search\n        automaton = ahocorasick.Automaton()\n        for term in terms:\n            automaton.add_word(term, term)\n        automaton.make_automaton()\n        return automaton\n\n\ndef get_glossary_terms(unit):\n    \"\"\"Return list of term pairs for an unit.\"\"\"\n    if unit.glossary_terms is not None:\n        return unit.glossary_terms\n    translation = unit.translation\n    language = translation.language\n    component = translation.component\n    project = component.project\n    source_language = component.source_language\n\n    units = (\n        Unit.objects.prefetch()\n        .select_related(\"source_unit\")\n        .order_by(\"translation__component__priority\", Lower(\"source\"))\n    )\n    if language == source_language:\n        return units.none()\n\n    # Build complete source for matching\n    parts = []\n    for text in unit.get_source_plurals():\n        text = text.lower().strip()\n        if text:\n            parts.append(text)\n    source = PLURAL_SEPARATOR.join(parts)\n\n    uses_ngram = source_language.uses_ngram()\n\n    terms = set()\n    automaton = project.glossary_automaton\n    if automaton.kind == ahocorasick.AHOCORASICK:\n        # Extract terms present in the source\n        with sentry_sdk.start_span(op=\"glossary.match\", description=project.slug):\n            for end, term in automaton.iter(source):\n                if uses_ngram or (\n                    (end + 1 == len(term) or NON_WORD_RE.match(source[end - len(term)]))\n                    and (end + 1 == len(source) or NON_WORD_RE.match(source[end + 1]))\n                ):\n                    terms.add(term)\n\n    if using_postgresql():\n        match = r\"^({})$\".format(\"|\".join(re_escape(term) for term in terms))\n        # Use regex as that is utilizing pg_trgm index\n        query = Q(source__iregex=match) | Q(variant__unit__source__iregex=match)\n    else:\n        # With MySQL we utilize it does case insensitive lookup\n        query = Q(source__in=terms) | Q(variant__unit__source__in=terms)\n\n    units = units.filter(\n        query,\n        translation__component__in=project.glossaries,\n        translation__component__source_language=source_language,\n        translation__language=language,\n    ).distinct()\n\n    # Store in a unit cache\n    unit.glossary_terms = units\n\n    return units\n", "path": "weblate/glossary/models.py"}, {"content": "# Copyright \u00a9 Michal \u010ciha\u0159 <michal@weblate.org>\n#\n# SPDX-License-Identifier: GPL-3.0-or-later\n\nimport sys\nfrom importlib.metadata import PackageNotFoundError, metadata\n\nfrom django.conf import settings\nfrom django.core.cache import cache\nfrom django.core.exceptions import ImproperlyConfigured\nfrom django.db import connection\n\nimport weblate.utils.version\nfrom weblate.utils.db import using_postgresql\nfrom weblate.utils.errors import report_error\nfrom weblate.vcs.git import GitRepository, GitWithGerritRepository, SubversionRepository\nfrom weblate.vcs.mercurial import HgRepository\n\nREQUIRES = [\n    \"Django\",\n    \"siphashc\",\n    \"translate-toolkit\",\n    \"lxml\",\n    \"Pillow\",\n    \"nh3\",\n    \"python-dateutil\",\n    \"social-auth-core\",\n    \"social-auth-app-django\",\n    \"django-crispy-forms\",\n    \"oauthlib\",\n    \"django-compressor\",\n    \"djangorestframework\",\n    \"django-filter\",\n    \"django-appconf\",\n    \"user-agents\",\n    \"filelock\",\n    \"rapidfuzz\",\n    \"openpyxl\",\n    \"celery\",\n    \"django-celery-beat\",\n    \"kombu\",\n    \"translation-finder\",\n    \"weblate-language-data\",\n    \"html2text\",\n    \"pycairo\",\n    \"pygobject\",\n    \"diff-match-patch\",\n    \"requests\",\n    \"django-redis\",\n    \"hiredis\",\n    \"sentry_sdk\",\n    \"Cython\",\n    \"misaka\",\n    \"GitPython\",\n    \"borgbackup\",\n    \"pyparsing\",\n    \"pyahocorasick\",\n    \"python-redis-lock\",\n    \"charset-normalizer\",\n]\n\nOPTIONAL = [\n    \"psycopg2\",\n    \"psycopg2-binary\",\n    \"phply\",\n    \"ruamel.yaml\",\n    \"tesserocr\",\n    \"akismet\",\n    \"boto3\",\n    \"zeep\",\n    \"aeidon\",\n    \"iniparse\",\n    \"mysqlclient\",\n]\n\n\ndef get_version_module(name, optional=False):\n    \"\"\"\n    Return module object.\n\n    On error raises verbose exception with name and URL.\n    \"\"\"\n    try:\n        package = metadata(name)\n    except PackageNotFoundError as exc:\n        if optional:\n            return None\n        raise ImproperlyConfigured(\n            f\"Missing dependency {name}, please install using: pip install {name}\"\n        ) from exc\n    url = package.get(\"Home-page\")\n    if url is None:\n        for project_url in package.get_all(\"Project-URL\"):\n            name, current_url = project_url.split(\",\", 1)\n            if name.lower().strip() == \"homepage\":\n                url = current_url.strip()\n                break\n    if url is None:\n        url = f\"https://pypi.org/project/{name}/\"\n    return (\n        package.get(\"Name\"),\n        url,\n        package.get(\"Version\"),\n    )\n\n\ndef get_optional_versions():\n    \"\"\"Return versions of optional modules.\"\"\"\n    result = []\n\n    for name in OPTIONAL:\n        module = get_version_module(name, True)\n        if module is not None:\n            result.append(module)\n\n    if HgRepository.is_supported():\n        result.append(\n            (\"Mercurial\", \"https://www.mercurial-scm.org/\", HgRepository.get_version())\n        )\n\n    if SubversionRepository.is_supported():\n        result.append(\n            (\n                \"git-svn\",\n                \"https://git-scm.com/docs/git-svn\",\n                SubversionRepository.get_version(),\n            )\n        )\n\n    if GitWithGerritRepository.is_supported():\n        result.append(\n            (\n                \"git-review\",\n                \"https://pypi.org/project/git-review/\",\n                GitWithGerritRepository.get_version(),\n            )\n        )\n\n    return result\n\n\ndef get_versions():\n    \"\"\"Return list of used versions.\"\"\"\n    result = [get_version_module(name) for name in REQUIRES]\n\n    result.append((\"Python\", \"https://www.python.org/\", sys.version.split()[0]))\n\n    try:\n        result.append((\"Git\", \"https://git-scm.com/\", GitRepository.get_version()))\n    except OSError as exc:\n        raise ImproperlyConfigured(\"Could not run git, please install it.\") from exc\n\n    return result\n\n\ndef get_db_version():\n    if using_postgresql():\n        try:\n            with connection.cursor() as cursor:\n                cursor.execute(\"SHOW server_version\")\n                version = cursor.fetchone()\n        except RuntimeError:\n            report_error(cause=\"PostgreSQL version check\")\n            return None\n\n        return (\n            \"PostgreSQL server\",\n            \"https://www.postgresql.org/\",\n            version[0].split(\" \")[0],\n        )\n    try:\n        with connection.cursor() as cursor:\n            version = cursor.connection.get_server_info()\n    except RuntimeError:\n        report_error(cause=\"MySQL version check\")\n        return None\n    return (\n        f\"{connection.display_name} sever\",\n        \"https://mariadb.org/\"\n        if connection.mysql_is_mariadb\n        else \"https://www.mysql.com/\",\n        version.split(\"-\", 1)[0],\n    )\n\n\ndef get_cache_version():\n    if settings.CACHES[\"default\"][\"BACKEND\"] == \"django_redis.cache.RedisCache\":\n        try:\n            version = cache.client.get_client().info()[\"redis_version\"]\n        except RuntimeError:\n            report_error(cause=\"Redis version check\")\n            return None\n\n        return (\"Redis server\", \"https://redis.io/\", version)\n\n    return None\n\n\ndef get_db_cache_version():\n    \"\"\"Returns the list of all the Database and Cache version.\"\"\"\n    result = []\n    cache_version = get_cache_version()\n    if cache_version:\n        result.append(cache_version)\n    db_version = get_db_version()\n    if db_version:\n        result.append(db_version)\n    return result\n\n\ndef get_versions_list():\n    \"\"\"Return list with version information summary.\"\"\"\n    return [\n        (\"Weblate\", \"https://weblate.org/\", weblate.utils.version.GIT_VERSION),\n        *get_versions(),\n        *get_optional_versions(),\n        *get_db_cache_version(),\n    ]\n", "path": "weblate/utils/requirements.py"}], "after_files": [{"content": "# Copyright \u00a9 Michal \u010ciha\u0159 <michal@weblate.org>\n#\n# SPDX-License-Identifier: GPL-3.0-or-later\n\nimport re\nfrom itertools import chain\n\nimport ahocorasick_rs\nimport sentry_sdk\nfrom django.db.models import Q\nfrom django.db.models.functions import Lower\n\nfrom weblate.trans.models.unit import Unit\nfrom weblate.trans.util import PLURAL_SEPARATOR\nfrom weblate.utils.db import re_escape, using_postgresql\nfrom weblate.utils.state import STATE_TRANSLATED\n\nSPLIT_RE = re.compile(r\"[\\s,.:!?]+\", re.UNICODE)\nNON_WORD_RE = re.compile(r\"\\W\", re.UNICODE)\n\n\ndef get_glossary_sources(component):\n    # Fetch list of terms defined in a translation\n    return list(\n        set(\n            component.source_translation.unit_set.filter(\n                state__gte=STATE_TRANSLATED\n            ).values_list(Lower(\"source\"), flat=True)\n        )\n    )\n\n\ndef get_glossary_automaton(project):\n    with sentry_sdk.start_span(op=\"glossary.automaton\", description=project.slug):\n        # Chain terms\n        terms = set(\n            chain.from_iterable(\n                glossary.glossary_sources for glossary in project.glossaries\n            )\n        )\n        # Build automaton for efficient Aho-Corasick search\n        return ahocorasick_rs.AhoCorasick(\n            terms,\n            implementation=ahocorasick_rs.Implementation.ContiguousNFA,\n            store_patterns=False,\n        )\n\n\ndef get_glossary_terms(unit):\n    \"\"\"Return list of term pairs for an unit.\"\"\"\n    if unit.glossary_terms is not None:\n        return unit.glossary_terms\n    translation = unit.translation\n    language = translation.language\n    component = translation.component\n    project = component.project\n    source_language = component.source_language\n\n    units = (\n        Unit.objects.prefetch()\n        .select_related(\"source_unit\")\n        .order_by(\"translation__component__priority\", Lower(\"source\"))\n    )\n    if language == source_language:\n        return units.none()\n\n    # Build complete source for matching\n    parts = []\n    for text in unit.get_source_plurals():\n        text = text.lower().strip()\n        if text:\n            parts.append(text)\n    source = PLURAL_SEPARATOR.join(parts)\n\n    uses_ngram = source_language.uses_ngram()\n\n    terms = set()\n    automaton = project.glossary_automaton\n    # Extract terms present in the source\n    with sentry_sdk.start_span(op=\"glossary.match\", description=project.slug):\n        for _termno, start, end in automaton.find_matches_as_indexes(\n            source, overlapping=True\n        ):\n            if uses_ngram or (\n                (start == 0 or NON_WORD_RE.match(source[start - 1]))\n                and (end >= len(source) or NON_WORD_RE.match(source[end]))\n            ):\n                terms.add(source[start:end])\n\n    if using_postgresql():\n        match = r\"^({})$\".format(\"|\".join(re_escape(term) for term in terms))\n        # Use regex as that is utilizing pg_trgm index\n        query = Q(source__iregex=match) | Q(variant__unit__source__iregex=match)\n    else:\n        # With MySQL we utilize it does case insensitive lookup\n        query = Q(source__in=terms) | Q(variant__unit__source__in=terms)\n\n    units = units.filter(\n        query,\n        translation__component__in=project.glossaries,\n        translation__component__source_language=source_language,\n        translation__language=language,\n    ).distinct()\n\n    # Store in a unit cache\n    unit.glossary_terms = units\n\n    return units\n", "path": "weblate/glossary/models.py"}, {"content": "# Copyright \u00a9 Michal \u010ciha\u0159 <michal@weblate.org>\n#\n# SPDX-License-Identifier: GPL-3.0-or-later\n\nimport sys\nfrom importlib.metadata import PackageNotFoundError, metadata\n\nfrom django.conf import settings\nfrom django.core.cache import cache\nfrom django.core.exceptions import ImproperlyConfigured\nfrom django.db import connection\n\nimport weblate.utils.version\nfrom weblate.utils.db import using_postgresql\nfrom weblate.utils.errors import report_error\nfrom weblate.vcs.git import GitRepository, GitWithGerritRepository, SubversionRepository\nfrom weblate.vcs.mercurial import HgRepository\n\nREQUIRES = [\n    \"Django\",\n    \"siphashc\",\n    \"translate-toolkit\",\n    \"lxml\",\n    \"Pillow\",\n    \"nh3\",\n    \"python-dateutil\",\n    \"social-auth-core\",\n    \"social-auth-app-django\",\n    \"django-crispy-forms\",\n    \"oauthlib\",\n    \"django-compressor\",\n    \"djangorestframework\",\n    \"django-filter\",\n    \"django-appconf\",\n    \"user-agents\",\n    \"filelock\",\n    \"rapidfuzz\",\n    \"openpyxl\",\n    \"celery\",\n    \"django-celery-beat\",\n    \"kombu\",\n    \"translation-finder\",\n    \"weblate-language-data\",\n    \"html2text\",\n    \"pycairo\",\n    \"pygobject\",\n    \"diff-match-patch\",\n    \"requests\",\n    \"django-redis\",\n    \"hiredis\",\n    \"sentry_sdk\",\n    \"Cython\",\n    \"misaka\",\n    \"GitPython\",\n    \"borgbackup\",\n    \"pyparsing\",\n    \"ahocorasick_rs\",\n    \"python-redis-lock\",\n    \"charset-normalizer\",\n]\n\nOPTIONAL = [\n    \"psycopg2\",\n    \"psycopg2-binary\",\n    \"phply\",\n    \"ruamel.yaml\",\n    \"tesserocr\",\n    \"akismet\",\n    \"boto3\",\n    \"zeep\",\n    \"aeidon\",\n    \"iniparse\",\n    \"mysqlclient\",\n]\n\n\ndef get_version_module(name, optional=False):\n    \"\"\"\n    Return module object.\n\n    On error raises verbose exception with name and URL.\n    \"\"\"\n    try:\n        package = metadata(name)\n    except PackageNotFoundError as exc:\n        if optional:\n            return None\n        raise ImproperlyConfigured(\n            f\"Missing dependency {name}, please install using: pip install {name}\"\n        ) from exc\n    url = package.get(\"Home-page\")\n    if url is None:\n        for project_url in package.get_all(\"Project-URL\"):\n            name, current_url = project_url.split(\",\", 1)\n            if name.lower().strip() == \"homepage\":\n                url = current_url.strip()\n                break\n    if url is None:\n        url = f\"https://pypi.org/project/{name}/\"\n    return (\n        package.get(\"Name\"),\n        url,\n        package.get(\"Version\"),\n    )\n\n\ndef get_optional_versions():\n    \"\"\"Return versions of optional modules.\"\"\"\n    result = []\n\n    for name in OPTIONAL:\n        module = get_version_module(name, True)\n        if module is not None:\n            result.append(module)\n\n    if HgRepository.is_supported():\n        result.append(\n            (\"Mercurial\", \"https://www.mercurial-scm.org/\", HgRepository.get_version())\n        )\n\n    if SubversionRepository.is_supported():\n        result.append(\n            (\n                \"git-svn\",\n                \"https://git-scm.com/docs/git-svn\",\n                SubversionRepository.get_version(),\n            )\n        )\n\n    if GitWithGerritRepository.is_supported():\n        result.append(\n            (\n                \"git-review\",\n                \"https://pypi.org/project/git-review/\",\n                GitWithGerritRepository.get_version(),\n            )\n        )\n\n    return result\n\n\ndef get_versions():\n    \"\"\"Return list of used versions.\"\"\"\n    result = [get_version_module(name) for name in REQUIRES]\n\n    result.append((\"Python\", \"https://www.python.org/\", sys.version.split()[0]))\n\n    try:\n        result.append((\"Git\", \"https://git-scm.com/\", GitRepository.get_version()))\n    except OSError as exc:\n        raise ImproperlyConfigured(\"Could not run git, please install it.\") from exc\n\n    return result\n\n\ndef get_db_version():\n    if using_postgresql():\n        try:\n            with connection.cursor() as cursor:\n                cursor.execute(\"SHOW server_version\")\n                version = cursor.fetchone()\n        except RuntimeError:\n            report_error(cause=\"PostgreSQL version check\")\n            return None\n\n        return (\n            \"PostgreSQL server\",\n            \"https://www.postgresql.org/\",\n            version[0].split(\" \")[0],\n        )\n    try:\n        with connection.cursor() as cursor:\n            version = cursor.connection.get_server_info()\n    except RuntimeError:\n        report_error(cause=\"MySQL version check\")\n        return None\n    return (\n        f\"{connection.display_name} sever\",\n        \"https://mariadb.org/\"\n        if connection.mysql_is_mariadb\n        else \"https://www.mysql.com/\",\n        version.split(\"-\", 1)[0],\n    )\n\n\ndef get_cache_version():\n    if settings.CACHES[\"default\"][\"BACKEND\"] == \"django_redis.cache.RedisCache\":\n        try:\n            version = cache.client.get_client().info()[\"redis_version\"]\n        except RuntimeError:\n            report_error(cause=\"Redis version check\")\n            return None\n\n        return (\"Redis server\", \"https://redis.io/\", version)\n\n    return None\n\n\ndef get_db_cache_version():\n    \"\"\"Returns the list of all the Database and Cache version.\"\"\"\n    result = []\n    cache_version = get_cache_version()\n    if cache_version:\n        result.append(cache_version)\n    db_version = get_db_version()\n    if db_version:\n        result.append(db_version)\n    return result\n\n\ndef get_versions_list():\n    \"\"\"Return list with version information summary.\"\"\"\n    return [\n        (\"Weblate\", \"https://weblate.org/\", weblate.utils.version.GIT_VERSION),\n        *get_versions(),\n        *get_optional_versions(),\n        *get_db_cache_version(),\n    ]\n", "path": "weblate/utils/requirements.py"}]}