eai_answers_lib 0.1.0__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.
- eai_answers_lib-0.1.0/PKG-INFO +49 -0
- eai_answers_lib-0.1.0/README.md +34 -0
- eai_answers_lib-0.1.0/pyproject.toml +40 -0
- eai_answers_lib-0.1.0/setup.cfg +4 -0
- eai_answers_lib-0.1.0/src/eai_answers_lib.egg-info/PKG-INFO +49 -0
- eai_answers_lib-0.1.0/src/eai_answers_lib.egg-info/SOURCES.txt +12 -0
- eai_answers_lib-0.1.0/src/eai_answers_lib.egg-info/dependency_links.txt +1 -0
- eai_answers_lib-0.1.0/src/eai_answers_lib.egg-info/requires.txt +8 -0
- eai_answers_lib-0.1.0/src/eai_answers_lib.egg-info/top_level.txt +2 -0
- eai_answers_lib-0.1.0/src/schemas/__init__.py +0 -0
- eai_answers_lib-0.1.0/src/schemas/answers_schemas.py +35 -0
- eai_answers_lib-0.1.0/src/services/__init__.py +0 -0
- eai_answers_lib-0.1.0/src/services/logger.py +16 -0
- eai_answers_lib-0.1.0/tests/test_answers.py +156 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: eai_answers_lib
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Library to interact with Expert AI Answers engine
|
|
5
|
+
Author: Simone Martin
|
|
6
|
+
Requires-Python: >=3.12
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: loguru>=0.7.3
|
|
9
|
+
Requires-Dist: pydantic>=2.13.3
|
|
10
|
+
Requires-Dist: requests>=2.33.1
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: ruff>=0.9.0; extra == "dev"
|
|
13
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
14
|
+
Requires-Dist: responses>=0.25.0; extra == "dev"
|
|
15
|
+
|
|
16
|
+
# Answers Lib
|
|
17
|
+
|
|
18
|
+
Library to interact with Expert AI Answers engine.
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install eai-answers-lib
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from eai_answers_lib import Answers
|
|
30
|
+
|
|
31
|
+
a = Answers()
|
|
32
|
+
result = a.get_answers(query="Your question")
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Authentication
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
a = Answers(
|
|
39
|
+
enable_auth=True,
|
|
40
|
+
client_id="your-client-id",
|
|
41
|
+
client_secret="your-client-secret"
|
|
42
|
+
)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Environment Variables
|
|
46
|
+
|
|
47
|
+
- `ANSWERS_ENDPOINT` - API endpoint URL
|
|
48
|
+
- `ANSWERS_CLIENT_ID` - Client ID for authentication
|
|
49
|
+
- `ANSWERS_CLIENT_SECRET` - Client secret for authentication
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Answers Lib
|
|
2
|
+
|
|
3
|
+
Library to interact with Expert AI Answers engine.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install eai-answers-lib
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from eai_answers_lib import Answers
|
|
15
|
+
|
|
16
|
+
a = Answers()
|
|
17
|
+
result = a.get_answers(query="Your question")
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Authentication
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
a = Answers(
|
|
24
|
+
enable_auth=True,
|
|
25
|
+
client_id="your-client-id",
|
|
26
|
+
client_secret="your-client-secret"
|
|
27
|
+
)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Environment Variables
|
|
31
|
+
|
|
32
|
+
- `ANSWERS_ENDPOINT` - API endpoint URL
|
|
33
|
+
- `ANSWERS_CLIENT_ID` - Client ID for authentication
|
|
34
|
+
- `ANSWERS_CLIENT_SECRET` - Client secret for authentication
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "eai_answers_lib"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Library to interact with Expert AI Answers engine"
|
|
5
|
+
authors = [{name = "Simone Martin"}]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
requires-python = ">=3.12"
|
|
8
|
+
dependencies = [
|
|
9
|
+
"loguru>=0.7.3",
|
|
10
|
+
"pydantic>=2.13.3",
|
|
11
|
+
"requests>=2.33.1",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[project.optional-dependencies]
|
|
15
|
+
dev = [
|
|
16
|
+
"ruff>=0.9.0",
|
|
17
|
+
"pytest>=8.0.0",
|
|
18
|
+
"responses>=0.25.0",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[tool.uv]
|
|
22
|
+
dev-dependencies = [
|
|
23
|
+
"ruff>=0.9.0",
|
|
24
|
+
"pytest>=8.0.0",
|
|
25
|
+
"responses>=0.25.0",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[tool.ruff]
|
|
29
|
+
target-version = "py312"
|
|
30
|
+
|
|
31
|
+
[tool.ruff.format]
|
|
32
|
+
quote-style = "double"
|
|
33
|
+
|
|
34
|
+
[tool.pytest.ini_options]
|
|
35
|
+
testpaths = ["tests"]
|
|
36
|
+
python_files = ["test_*.py", "*_test.py"]
|
|
37
|
+
python_functions = ["test_*"]
|
|
38
|
+
|
|
39
|
+
[tool.setuptools.packages.find]
|
|
40
|
+
where = ["src"]
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: eai_answers_lib
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Library to interact with Expert AI Answers engine
|
|
5
|
+
Author: Simone Martin
|
|
6
|
+
Requires-Python: >=3.12
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: loguru>=0.7.3
|
|
9
|
+
Requires-Dist: pydantic>=2.13.3
|
|
10
|
+
Requires-Dist: requests>=2.33.1
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: ruff>=0.9.0; extra == "dev"
|
|
13
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
14
|
+
Requires-Dist: responses>=0.25.0; extra == "dev"
|
|
15
|
+
|
|
16
|
+
# Answers Lib
|
|
17
|
+
|
|
18
|
+
Library to interact with Expert AI Answers engine.
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install eai-answers-lib
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from eai_answers_lib import Answers
|
|
30
|
+
|
|
31
|
+
a = Answers()
|
|
32
|
+
result = a.get_answers(query="Your question")
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Authentication
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
a = Answers(
|
|
39
|
+
enable_auth=True,
|
|
40
|
+
client_id="your-client-id",
|
|
41
|
+
client_secret="your-client-secret"
|
|
42
|
+
)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Environment Variables
|
|
46
|
+
|
|
47
|
+
- `ANSWERS_ENDPOINT` - API endpoint URL
|
|
48
|
+
- `ANSWERS_CLIENT_ID` - Client ID for authentication
|
|
49
|
+
- `ANSWERS_CLIENT_SECRET` - Client secret for authentication
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/eai_answers_lib.egg-info/PKG-INFO
|
|
4
|
+
src/eai_answers_lib.egg-info/SOURCES.txt
|
|
5
|
+
src/eai_answers_lib.egg-info/dependency_links.txt
|
|
6
|
+
src/eai_answers_lib.egg-info/requires.txt
|
|
7
|
+
src/eai_answers_lib.egg-info/top_level.txt
|
|
8
|
+
src/schemas/__init__.py
|
|
9
|
+
src/schemas/answers_schemas.py
|
|
10
|
+
src/services/__init__.py
|
|
11
|
+
src/services/logger.py
|
|
12
|
+
tests/test_answers.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
File without changes
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
|
|
2
|
+
from typing import Any, Dict, Optional, Union, List, Literal
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class PackageAndCollection(BaseModel):
|
|
9
|
+
kb: str
|
|
10
|
+
description: str # collection description
|
|
11
|
+
id: str # collection id
|
|
12
|
+
|
|
13
|
+
class CollectionDocument(BaseModel):
|
|
14
|
+
data: str
|
|
15
|
+
dataAnnotation: Optional[str] = None
|
|
16
|
+
dataXml: Optional[str] = None
|
|
17
|
+
dataJson: Optional[Dict[Any, Any]] = None
|
|
18
|
+
size: Optional[int] = None
|
|
19
|
+
name: str
|
|
20
|
+
|
|
21
|
+
class AddCollectionRequest(BaseModel):
|
|
22
|
+
cogitoAnalysisMode: Optional[str] = None
|
|
23
|
+
removeAnnotations: Optional[bool] = True
|
|
24
|
+
ocr: Optional[bool] = False
|
|
25
|
+
doubleColumn: Optional[bool] = False
|
|
26
|
+
pageImages: Optional[bool] = False
|
|
27
|
+
indexStrategies: List[str]
|
|
28
|
+
engine: str
|
|
29
|
+
analyzer: Optional[str] = None
|
|
30
|
+
status: Optional[str] = None
|
|
31
|
+
messages: Optional[List[Dict[str, str]]] = None
|
|
32
|
+
documents: List[CollectionDocument]
|
|
33
|
+
pkg: str
|
|
34
|
+
collection: str
|
|
35
|
+
|
|
File without changes
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from loguru import logger
|
|
3
|
+
|
|
4
|
+
logs_path = Path(".logs")
|
|
5
|
+
logs_path.mkdir(parents=True, exist_ok=True)
|
|
6
|
+
|
|
7
|
+
#logger.remove() # Remove the default console logger
|
|
8
|
+
logger.add(
|
|
9
|
+
logs_path / "log_{time:YYYY-MM-DD}.log", # File name with date
|
|
10
|
+
rotation="1 day",
|
|
11
|
+
retention="2 months", # Keep 7 days of logs
|
|
12
|
+
# compression="zip", # Compress old logs
|
|
13
|
+
level="INFO", # Only log INFO and above
|
|
14
|
+
enqueue=True # Thread-safe logging
|
|
15
|
+
)
|
|
16
|
+
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import responses
|
|
3
|
+
from src import Answers
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
responses.mock.start()
|
|
7
|
+
responses.mock.register_uri(
|
|
8
|
+
responses.POST,
|
|
9
|
+
"https://api.example.com/auth/realms/test-realm/protocol/openid-connect/token",
|
|
10
|
+
json={"access_token": "test-token", "expires_in": 3600},
|
|
11
|
+
status=200,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.fixture
|
|
16
|
+
def mock_endpoint():
|
|
17
|
+
return "https://api.example.com"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@pytest.fixture
|
|
21
|
+
def answers(mock_endpoint):
|
|
22
|
+
return Answers(endpoint=mock_endpoint, enable_auth=False)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@pytest.fixture
|
|
26
|
+
def answers_with_auth(mock_endpoint):
|
|
27
|
+
return Answers(
|
|
28
|
+
endpoint=mock_endpoint,
|
|
29
|
+
enable_auth=True,
|
|
30
|
+
auth_path="auth",
|
|
31
|
+
realm="test-realm",
|
|
32
|
+
client_id="test-client",
|
|
33
|
+
client_secret="test-secret",
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class TestInit:
|
|
38
|
+
def test_init_success(self, mock_endpoint):
|
|
39
|
+
answers = Answers(endpoint=mock_endpoint)
|
|
40
|
+
assert answers.endpoint == mock_endpoint
|
|
41
|
+
|
|
42
|
+
def test_init_missing_endpoint_raises(self):
|
|
43
|
+
with pytest.raises(ValueError, match="endpoint must be provided"):
|
|
44
|
+
Answers(endpoint=None)
|
|
45
|
+
|
|
46
|
+
def test_init_with_auth(self, mock_endpoint):
|
|
47
|
+
answers = Answers(
|
|
48
|
+
endpoint=mock_endpoint,
|
|
49
|
+
enable_auth=True,
|
|
50
|
+
auth_path="auth",
|
|
51
|
+
realm="test-realm",
|
|
52
|
+
client_id="client",
|
|
53
|
+
client_secret="secret",
|
|
54
|
+
)
|
|
55
|
+
assert answers.enable_auth is True
|
|
56
|
+
assert answers.auth_path == "auth"
|
|
57
|
+
assert answers.realm == "test-realm"
|
|
58
|
+
|
|
59
|
+
def test_init_default_paths(self, mock_endpoint):
|
|
60
|
+
answers = Answers(endpoint=mock_endpoint)
|
|
61
|
+
assert answers.query_path == "v1/query"
|
|
62
|
+
assert answers.status_path == "v1/status"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_refresh_bearer_success(answers_with_auth):
|
|
66
|
+
responses.mock.register_uri(
|
|
67
|
+
responses.POST,
|
|
68
|
+
"https://api.example.com/auth/realms/test-realm/protocol/openid-connect/token",
|
|
69
|
+
json={"access_token": "new-token", "expires_in": 3600},
|
|
70
|
+
status=200,
|
|
71
|
+
)
|
|
72
|
+
answers_with_auth.refresh_bearer()
|
|
73
|
+
assert answers_with_auth.bearer == "new-token"
|
|
74
|
+
assert answers_with_auth.token_expiration > 0
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_refresh_bearer_failure(answers_with_auth):
|
|
78
|
+
responses.mock.register_uri(
|
|
79
|
+
responses.POST,
|
|
80
|
+
"https://api.example.com/auth/realms/test-realm/protocol/openid-connect/token",
|
|
81
|
+
status=401,
|
|
82
|
+
)
|
|
83
|
+
with pytest.raises(Exception, match="Error Refreshing authentication Bearer"):
|
|
84
|
+
answers_with_auth.refresh_bearer()
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_get_auth_header_no_auth(answers):
|
|
88
|
+
headers = answers.get_auth_header()
|
|
89
|
+
assert headers == {}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_get_auth_header_with_auth(answers_with_auth):
|
|
93
|
+
headers = answers_with_auth.get_auth_header()
|
|
94
|
+
assert headers == {"Authorization": "Bearer test-token"}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@responses.activate
|
|
98
|
+
def test_start_query_task_success(answers):
|
|
99
|
+
responses.post(
|
|
100
|
+
"https://api.example.com/v1/query/pkg/collection",
|
|
101
|
+
json={"success": True, "data": {"task_id": "task-123"}},
|
|
102
|
+
status=200,
|
|
103
|
+
)
|
|
104
|
+
task_id = answers.start_query_task(payload={}, headers={}, pkg="pkg", collection="collection")
|
|
105
|
+
assert task_id == "task-123"
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@responses.activate
|
|
109
|
+
def test_start_query_task_failure(answers):
|
|
110
|
+
responses.post(
|
|
111
|
+
"https://api.example.com/v1/query/pkg/collection",
|
|
112
|
+
json={"success": False, "data": {"error": "failed"}},
|
|
113
|
+
status=200,
|
|
114
|
+
)
|
|
115
|
+
with pytest.raises(Exception):
|
|
116
|
+
answers.start_query_task(payload={}, headers={}, pkg="pkg", collection="collection")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@responses.activate
|
|
120
|
+
def test_poll_query_status_success(answers):
|
|
121
|
+
responses.get(
|
|
122
|
+
"https://api.example.com/v1/status/task-123",
|
|
123
|
+
json={"success": True, "data": {"completed": True, "query_tasks": ["t1"], "completed_tasks": ["t1"]}},
|
|
124
|
+
status=200,
|
|
125
|
+
)
|
|
126
|
+
status = answers.poll_query_status(task_id="task-123", headers={})
|
|
127
|
+
assert status["success"] is True
|
|
128
|
+
assert status["data"]["completed"] is True
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@responses.activate
|
|
132
|
+
def test_poll_query_status_failure(answers):
|
|
133
|
+
responses.get(
|
|
134
|
+
"https://api.example.com/v1/status/task-123",
|
|
135
|
+
json={"success": False, "messages": ["error"]},
|
|
136
|
+
status=200,
|
|
137
|
+
)
|
|
138
|
+
status = answers.poll_query_status(task_id="task-123", headers={})
|
|
139
|
+
assert status["success"] is False
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@responses.activate
|
|
143
|
+
def test_call_full_flow(answers):
|
|
144
|
+
responses.post(
|
|
145
|
+
"https://api.example.com/v1/query/pkg/collection",
|
|
146
|
+
json={"success": True, "data": {"task_id": "task-456"}},
|
|
147
|
+
status=200,
|
|
148
|
+
)
|
|
149
|
+
responses.get(
|
|
150
|
+
"https://api.example.com/v1/status/task-456",
|
|
151
|
+
json={"success": True, "data": {"completed": True}},
|
|
152
|
+
status=200,
|
|
153
|
+
)
|
|
154
|
+
result = answers(payload={}, pkg="pkg", collection="collection")
|
|
155
|
+
assert result["success"] is True
|
|
156
|
+
assert result["data"]["completed"] is True
|