Skip to content

Commit b63709f

Browse files
committed
Adding the rest of analyzers API, plus tests
1 parent f7eebdf commit b63709f

File tree

5 files changed

+235
-1
lines changed

5 files changed

+235
-1
lines changed

arangoasync/database.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
from arangoasync.connection import Connection
1515
from arangoasync.errno import HTTP_FORBIDDEN, HTTP_NOT_FOUND
1616
from arangoasync.exceptions import (
17+
AnalyzerCreateError,
18+
AnalyzerDeleteError,
19+
AnalyzerGetError,
1720
AnalyzerListError,
1821
AsyncJobClearError,
1922
AsyncJobListError,
@@ -1484,6 +1487,111 @@ def response_handler(resp: Response) -> Jsons:
14841487

14851488
return await self._executor.execute(request, response_handler)
14861489

1490+
async def analyzer(self, name: str) -> Result[Json]:
1491+
"""Return analyzer details.
1492+
1493+
Args:
1494+
name (str): Analyzer name.
1495+
1496+
Returns:
1497+
dict: Analyzer properties.
1498+
1499+
References:
1500+
- `get-an-analyzer-definition <https://docs.arangodb.com/stable/develop/http-api/analyzers/#get-an-analyzer-definition>`__
1501+
""" # noqa: E501
1502+
request = Request(method=Method.GET, endpoint=f"/_api/analyzer/{name}")
1503+
1504+
def response_handler(resp: Response) -> Json:
1505+
if not resp.is_success:
1506+
raise AnalyzerGetError(resp, request)
1507+
return Response.format_body(self.deserializer.loads(resp.raw_body))
1508+
1509+
return await self._executor.execute(request, response_handler)
1510+
1511+
async def create_analyzer(
1512+
self,
1513+
name: str,
1514+
analyzer_type: str,
1515+
properties: Optional[Json] = None,
1516+
features: Optional[Sequence[str]] = None,
1517+
) -> Result[Json]:
1518+
"""Create an analyzer.
1519+
1520+
Args:
1521+
name (str): Analyzer name.
1522+
analyzer_type (str): Type of the analyzer (e.g., "text", "identity").
1523+
properties (dict | None): Properties of the analyzer.
1524+
features (list | None): The set of features to set on the Analyzer
1525+
generated fields. The default value is an empty array. Possible values:
1526+
"frequency", "norm", "position", "offset".
1527+
1528+
Returns:
1529+
dict: Analyzer properties.
1530+
1531+
Raises:
1532+
AnalyzerCreateError: If the operation fails.
1533+
1534+
References:
1535+
- `create-an-analyzer <https://docs.arangodb.com/stable/develop/http-api/analyzers/#create-an-analyzer>`__
1536+
""" # noqa: E501
1537+
data: Json = {"name": name, "type": analyzer_type}
1538+
if properties is not None:
1539+
data["properties"] = properties
1540+
if features is not None:
1541+
data["features"] = features
1542+
1543+
request = Request(
1544+
method=Method.POST,
1545+
endpoint="/_api/analyzer",
1546+
data=self.serializer.dumps(data),
1547+
)
1548+
1549+
def response_handler(resp: Response) -> Json:
1550+
if not resp.is_success:
1551+
raise AnalyzerCreateError(resp, request)
1552+
return self.deserializer.loads(resp.raw_body)
1553+
1554+
return await self._executor.execute(request, response_handler)
1555+
1556+
async def delete_analyzer(
1557+
self, name: str, force: Optional[bool] = None, ignore_missing: bool = False
1558+
) -> Result[bool]:
1559+
"""Delete an analyzer.
1560+
1561+
Args:
1562+
name (str): Analyzer name.
1563+
force (bool | None): Remove the analyzer configuration even if in use.
1564+
ignore_missing (bool): Do not raise an exception on missing analyzer.
1565+
1566+
Returns:
1567+
bool: `True` if the analyzer was deleted successfully, `False` if the
1568+
analyzer was not found and **ignore_missing** was set to `True`.
1569+
1570+
Raises:
1571+
AnalyzerDeleteError: If the operation fails.
1572+
1573+
References:
1574+
- `remove-an-analyzer <https://docs.arangodb.com/stable/develop/http-api/analyzers/#remove-an-analyzer>`__
1575+
""" # noqa: E501
1576+
params: Params = {}
1577+
if force is not None:
1578+
params["force"] = force
1579+
1580+
request = Request(
1581+
method=Method.DELETE,
1582+
endpoint=f"/_api/analyzer/{name}",
1583+
params=params,
1584+
)
1585+
1586+
def response_handler(resp: Response) -> bool:
1587+
if resp.is_success:
1588+
return True
1589+
if resp.status_code == HTTP_NOT_FOUND and ignore_missing:
1590+
return False
1591+
raise AnalyzerDeleteError(resp, request)
1592+
1593+
return await self._executor.execute(request, response_handler)
1594+
14871595
async def has_user(self, username: str) -> Result[bool]:
14881596
"""Check if a user exists.
14891597

arangoasync/exceptions.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,18 @@ class AQLQueryValidateError(ArangoServerError):
135135
"""Failed to parse and validate query."""
136136

137137

138+
class AnalyzerCreateError(ArangoServerError):
139+
"""Failed to create analyzer."""
140+
141+
142+
class AnalyzerGetError(ArangoServerError):
143+
"""Failed to retrieve analyzer details."""
144+
145+
146+
class AnalyzerDeleteError(ArangoServerError):
147+
"""Failed to delete analyzer."""
148+
149+
138150
class AnalyzerListError(ArangoServerError):
139151
"""Failed to retrieve analyzers."""
140152

arangoasync/response.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from typing import Optional
66

77
from arangoasync.request import Method
8-
from arangoasync.typings import ResponseHeaders
8+
from arangoasync.typings import Json, ResponseHeaders
99

1010

1111
class Response:
@@ -63,3 +63,17 @@ def __init__(
6363
self.error_code: Optional[int] = None
6464
self.error_message: Optional[str] = None
6565
self.is_success: Optional[bool] = None
66+
67+
@staticmethod
68+
def format_body(body: Json) -> Json:
69+
"""Format the generic response body, stripping the error code and message.
70+
71+
Args:
72+
body (Json): The response body.
73+
74+
Returns:
75+
dict: The formatted response body.
76+
"""
77+
body.pop("error", None)
78+
body.pop("code", None)
79+
return body

tests/helpers.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,12 @@ def generate_view_name():
5353
str: Random view name.
5454
"""
5555
return f"test_view_{uuid4().hex}"
56+
57+
58+
def generate_analyzer_name():
59+
"""Generate and return a random analyzer name.
60+
61+
Returns:
62+
str: Random analyzer name.
63+
"""
64+
return f"test_analyzer_{uuid4().hex}"

tests/test_analyzer.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import pytest
2+
from packaging import version
3+
4+
from arangoasync.exceptions import (
5+
AnalyzerCreateError,
6+
AnalyzerDeleteError,
7+
AnalyzerGetError,
8+
AnalyzerListError,
9+
)
10+
from tests.helpers import generate_analyzer_name
11+
12+
13+
@pytest.mark.asyncio
14+
async def test_analyzer_management(db, bad_db, enterprise, db_version):
15+
analyzer_name = generate_analyzer_name()
16+
full_analyzer_name = db.name + "::" + analyzer_name
17+
bad_analyzer_name = generate_analyzer_name()
18+
19+
# Test create identity analyzer
20+
result = await db.create_analyzer(analyzer_name, "identity")
21+
assert result["name"] == full_analyzer_name
22+
assert result["type"] == "identity"
23+
assert result["properties"] == {}
24+
assert result["features"] == []
25+
26+
# Test create delimiter analyzer
27+
result = await db.create_analyzer(
28+
name=generate_analyzer_name(),
29+
analyzer_type="delimiter",
30+
properties={"delimiter": ","},
31+
)
32+
assert result["type"] == "delimiter"
33+
assert result["properties"] == {"delimiter": ","}
34+
assert result["features"] == []
35+
36+
# Test create duplicate with bad database
37+
with pytest.raises(AnalyzerCreateError):
38+
await bad_db.create_analyzer(analyzer_name, "identity")
39+
40+
# Test get analyzer
41+
result = await db.analyzer(analyzer_name)
42+
assert result["name"] == full_analyzer_name
43+
assert result["type"] == "identity"
44+
assert result["properties"] == {}
45+
assert result["features"] == []
46+
47+
# Test get missing analyzer
48+
with pytest.raises(AnalyzerGetError):
49+
await db.analyzer(bad_analyzer_name)
50+
51+
# Test list analyzers
52+
result = await db.analyzers()
53+
assert full_analyzer_name in [a["name"] for a in result]
54+
55+
# Test list analyzers with bad database
56+
with pytest.raises(AnalyzerListError):
57+
await bad_db.analyzers()
58+
59+
# Test delete analyzer
60+
assert await db.delete_analyzer(analyzer_name, force=True) is True
61+
assert full_analyzer_name not in [a["name"] for a in await db.analyzers()]
62+
63+
# Test delete missing analyzer
64+
with pytest.raises(AnalyzerDeleteError):
65+
await db.delete_analyzer(analyzer_name)
66+
67+
# Test delete missing analyzer with ignore_missing set to True
68+
assert await db.delete_analyzer(analyzer_name, ignore_missing=True) is False
69+
70+
# Test create geo_s2 analyzer
71+
if enterprise:
72+
analyzer_name = generate_analyzer_name()
73+
result = await db.create_analyzer(analyzer_name, "geo_s2", properties={})
74+
assert result["type"] == "geo_s2"
75+
assert await db.delete_analyzer(analyzer_name)
76+
77+
if db_version >= version.parse("3.12.0"):
78+
# Test delimiter analyzer with multiple delimiters
79+
result = await db.create_analyzer(
80+
name=generate_analyzer_name(),
81+
analyzer_type="multi_delimiter",
82+
properties={"delimiters": [",", "."]},
83+
)
84+
assert result["type"] == "multi_delimiter"
85+
assert result["properties"] == {"delimiters": [",", "."]}
86+
87+
# Test wildcard analyzer
88+
analyzer_name = generate_analyzer_name()
89+
result = await db.create_analyzer(analyzer_name, "wildcard", {"ngramSize": 4})
90+
assert result["type"] == "wildcard"
91+
assert result["properties"] == {"ngramSize": 4}

0 commit comments

Comments
 (0)