guardianhub 0.1.44__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.
- guardianhub-0.1.44/LICENSE +21 -0
- guardianhub-0.1.44/PKG-INFO +120 -0
- guardianhub-0.1.44/README.md +84 -0
- guardianhub-0.1.44/pyproject.toml +49 -0
- guardianhub-0.1.44/src/guardianhub/__init__.py +17 -0
- guardianhub-0.1.44/src/guardianhub/_version.py +3 -0
- guardianhub-0.1.44/src/guardianhub/agents/runtime.py +12 -0
- guardianhub-0.1.44/src/guardianhub/auth/token_provider.py +22 -0
- guardianhub-0.1.44/src/guardianhub/clients/__init__.py +2 -0
- guardianhub-0.1.44/src/guardianhub/clients/classification_client.py +52 -0
- guardianhub-0.1.44/src/guardianhub/clients/graph_db_client.py +161 -0
- guardianhub-0.1.44/src/guardianhub/clients/llm.py +2 -0
- guardianhub-0.1.44/src/guardianhub/clients/llm_client.py +145 -0
- guardianhub-0.1.44/src/guardianhub/clients/llm_service.py +297 -0
- guardianhub-0.1.44/src/guardianhub/clients/metadata_extractor_client.py +53 -0
- guardianhub-0.1.44/src/guardianhub/clients/ocr_client.py +80 -0
- guardianhub-0.1.44/src/guardianhub/clients/paperless_client.py +518 -0
- guardianhub-0.1.44/src/guardianhub/clients/registry_client.py +18 -0
- guardianhub-0.1.44/src/guardianhub/clients/text_cleaner_client.py +53 -0
- guardianhub-0.1.44/src/guardianhub/clients/vector_client.py +345 -0
- guardianhub-0.1.44/src/guardianhub/config/__init__.py +2 -0
- guardianhub-0.1.44/src/guardianhub/config/config.json +39 -0
- guardianhub-0.1.44/src/guardianhub/config/config_dev.json +41 -0
- guardianhub-0.1.44/src/guardianhub/config/settings.py +137 -0
- guardianhub-0.1.44/src/guardianhub/http/http_client.py +26 -0
- guardianhub-0.1.44/src/guardianhub/logging/__init__.py +2 -0
- guardianhub-0.1.44/src/guardianhub/logging/logging.py +70 -0
- guardianhub-0.1.44/src/guardianhub/models/__init__.py +1 -0
- guardianhub-0.1.44/src/guardianhub/models/base.py +2 -0
- guardianhub-0.1.44/src/guardianhub/models/registry/client.py +16 -0
- guardianhub-0.1.44/src/guardianhub/models/registry/dynamic_loader.py +91 -0
- guardianhub-0.1.44/src/guardianhub/models/registry/loader.py +37 -0
- guardianhub-0.1.44/src/guardianhub/models/registry/registry.py +17 -0
- guardianhub-0.1.44/src/guardianhub/models/registry/signing.py +70 -0
- guardianhub-0.1.44/src/guardianhub/models/template/__init__.py +0 -0
- guardianhub-0.1.44/src/guardianhub/models/template/agent_response_evaluation.py +62 -0
- guardianhub-0.1.44/src/guardianhub/models/template/extraction.py +29 -0
- guardianhub-0.1.44/src/guardianhub/models/template/suggestion.py +42 -0
- guardianhub-0.1.44/src/guardianhub/observability/__init__.py +2 -0
- guardianhub-0.1.44/src/guardianhub/observability/instrumentation.py +267 -0
- guardianhub-0.1.44/src/guardianhub/prompts/base.py +7 -0
- guardianhub-0.1.44/src/guardianhub/prompts/providers/langfuse_provider.py +13 -0
- guardianhub-0.1.44/src/guardianhub/prompts/providers/local_provider.py +22 -0
- guardianhub-0.1.44/src/guardianhub/prompts/registry.py +14 -0
- guardianhub-0.1.44/src/guardianhub/scripts/script.sh +31 -0
- guardianhub-0.1.44/src/guardianhub/services/base.py +16 -0
- guardianhub-0.1.44/src/guardianhub/template/__init__.py +0 -0
- guardianhub-0.1.44/src/guardianhub/tools/gh_registry_cli.py +172 -0
- guardianhub-0.1.44/src/guardianhub/utils/__init__.py +0 -0
- guardianhub-0.1.44/src/guardianhub/utils/app_state.py +74 -0
- guardianhub-0.1.44/src/guardianhub/utils/fastapi_utils.py +141 -0
- guardianhub-0.1.44/src/guardianhub/utils/json_utils.py +139 -0
- guardianhub-0.1.44/src/guardianhub/utils/metrics.py +60 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Rashmi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: guardianhub
|
|
3
|
+
Version: 0.1.44
|
|
4
|
+
Summary: Unified SDK for Local AI Guardian
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Author: Jayant
|
|
8
|
+
Author-email: jayant@yantramops.com
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Provides-Extra: dev
|
|
18
|
+
Requires-Dist: click (>=8.0.0)
|
|
19
|
+
Requires-Dist: fastapi (>=0.123.0)
|
|
20
|
+
Requires-Dist: httpcore (>=0.17.0)
|
|
21
|
+
Requires-Dist: httpx (>=0.24.0)
|
|
22
|
+
Requires-Dist: opentelemetry-api (>=1.38.0)
|
|
23
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http (>=1.38.0)
|
|
24
|
+
Requires-Dist: opentelemetry-instrumentation-fastapi (>=0.59b0)
|
|
25
|
+
Requires-Dist: opentelemetry-instrumentation-httpx (>=0.59b0)
|
|
26
|
+
Requires-Dist: opentelemetry-sdk (>=1.38.0)
|
|
27
|
+
Requires-Dist: prometheus-client (>=0.23.1)
|
|
28
|
+
Requires-Dist: pycryptodome (>=3.18.0)
|
|
29
|
+
Requires-Dist: pydantic (>=2.0.0)
|
|
30
|
+
Requires-Dist: pydantic-settings (>=2.0.0)
|
|
31
|
+
Requires-Dist: pytest (>=7.0.0) ; extra == "dev"
|
|
32
|
+
Requires-Dist: pytest-asyncio (>=0.20.0) ; extra == "dev"
|
|
33
|
+
Project-URL: Homepage, https://github.com/yantramai/guardianhub-sdk
|
|
34
|
+
Project-URL: Source, https://github.com/yantramai/guardianhub-sdk
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
|
|
37
|
+
# GuardianHub SDK
|
|
38
|
+
|
|
39
|
+
A Python SDK for interacting with GuardianHub services, featuring a model registry and LLM client.
|
|
40
|
+
|
|
41
|
+
## Features
|
|
42
|
+
|
|
43
|
+
- Model registry with versioning
|
|
44
|
+
- Async HTTP client for API interactions
|
|
45
|
+
- Pydantic model support
|
|
46
|
+
- Local development and testing tools
|
|
47
|
+
|
|
48
|
+
## Installation
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# Install from PyPI (when published)
|
|
52
|
+
ssh-keygen -t ed25519 -C "rashmi@yantramops.com"
|
|
53
|
+
pip install guardianhub-sdk
|
|
54
|
+
|
|
55
|
+
# Or install in development mode
|
|
56
|
+
pip install -e .[test]
|
|
57
|
+
|
|
58
|
+
# 1. Remove the specified directory and its contents from the Git index.
|
|
59
|
+
# The --cached flag ensures the files are NOT deleted from your local disk.
|
|
60
|
+
git rm -r --cached .github
|
|
61
|
+
|
|
62
|
+
# 2. Add the .gitignore file (which now contains the new rules)
|
|
63
|
+
git add .gitignore
|
|
64
|
+
|
|
65
|
+
# 3. Commit the changes
|
|
66
|
+
git commit -m "Cleanup: Remove .github directory from tracking and update .gitignore"
|
|
67
|
+
|
|
68
|
+
git checkout -b dev
|
|
69
|
+
git push -u origin dev
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
git checkout dev
|
|
73
|
+
git pull origin dev # Ensure you have the latest code
|
|
74
|
+
git checkout -b feature/model-registry # Name your branch descriptively
|
|
75
|
+
git push -u origin feature/model-registry
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Usage
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from guardianhub_sdk.models.registry.loader import RegistryLoader
|
|
83
|
+
|
|
84
|
+
# Initialize the loader
|
|
85
|
+
loader = RegistryLoader()
|
|
86
|
+
|
|
87
|
+
# Load a model
|
|
88
|
+
model = await loader.load_model("UserModel", version="v1")
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Development
|
|
92
|
+
|
|
93
|
+
### Setup
|
|
94
|
+
|
|
95
|
+
1. Clone the repository:
|
|
96
|
+
```bash
|
|
97
|
+
git clone https://github.com/your-username/guardianhub-sdk.git
|
|
98
|
+
cd guardianhub-sdk
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
2. Create a virtual environment:
|
|
102
|
+
```bash
|
|
103
|
+
python -m venv .venv
|
|
104
|
+
source .venv/bin/activate # On Windows: .venv\Scripts\activate
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
3. Install dependencies:
|
|
108
|
+
```bash
|
|
109
|
+
pip install -e .[test]
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Running Tests
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
pytest tests/
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## License
|
|
119
|
+
|
|
120
|
+
MIT
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# GuardianHub SDK
|
|
2
|
+
|
|
3
|
+
A Python SDK for interacting with GuardianHub services, featuring a model registry and LLM client.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Model registry with versioning
|
|
8
|
+
- Async HTTP client for API interactions
|
|
9
|
+
- Pydantic model support
|
|
10
|
+
- Local development and testing tools
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
# Install from PyPI (when published)
|
|
16
|
+
ssh-keygen -t ed25519 -C "rashmi@yantramops.com"
|
|
17
|
+
pip install guardianhub-sdk
|
|
18
|
+
|
|
19
|
+
# Or install in development mode
|
|
20
|
+
pip install -e .[test]
|
|
21
|
+
|
|
22
|
+
# 1. Remove the specified directory and its contents from the Git index.
|
|
23
|
+
# The --cached flag ensures the files are NOT deleted from your local disk.
|
|
24
|
+
git rm -r --cached .github
|
|
25
|
+
|
|
26
|
+
# 2. Add the .gitignore file (which now contains the new rules)
|
|
27
|
+
git add .gitignore
|
|
28
|
+
|
|
29
|
+
# 3. Commit the changes
|
|
30
|
+
git commit -m "Cleanup: Remove .github directory from tracking and update .gitignore"
|
|
31
|
+
|
|
32
|
+
git checkout -b dev
|
|
33
|
+
git push -u origin dev
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
git checkout dev
|
|
37
|
+
git pull origin dev # Ensure you have the latest code
|
|
38
|
+
git checkout -b feature/model-registry # Name your branch descriptively
|
|
39
|
+
git push -u origin feature/model-registry
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Usage
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from guardianhub_sdk.models.registry.loader import RegistryLoader
|
|
47
|
+
|
|
48
|
+
# Initialize the loader
|
|
49
|
+
loader = RegistryLoader()
|
|
50
|
+
|
|
51
|
+
# Load a model
|
|
52
|
+
model = await loader.load_model("UserModel", version="v1")
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Development
|
|
56
|
+
|
|
57
|
+
### Setup
|
|
58
|
+
|
|
59
|
+
1. Clone the repository:
|
|
60
|
+
```bash
|
|
61
|
+
git clone https://github.com/your-username/guardianhub-sdk.git
|
|
62
|
+
cd guardianhub-sdk
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
2. Create a virtual environment:
|
|
66
|
+
```bash
|
|
67
|
+
python -m venv .venv
|
|
68
|
+
source .venv/bin/activate # On Windows: .venv\Scripts\activate
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
3. Install dependencies:
|
|
72
|
+
```bash
|
|
73
|
+
pip install -e .[test]
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Running Tests
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pytest tests/
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## License
|
|
83
|
+
|
|
84
|
+
MIT
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["poetry-core>=1.0.0"]
|
|
3
|
+
build-backend = "poetry.core.masonry.api"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "guardianhub"
|
|
7
|
+
version = "0.1.44" # This will be overridden by Hatch's dynamic version
|
|
8
|
+
# Note: Keep _version.py as the source of truth for version
|
|
9
|
+
description = "Unified SDK for Local AI Guardian"
|
|
10
|
+
authors = [
|
|
11
|
+
{name = "Jayant", email = "jayant@yantramops.com"}
|
|
12
|
+
]
|
|
13
|
+
readme = "README.md"
|
|
14
|
+
license = {text = "MIT"}
|
|
15
|
+
requires-python = ">=3.10"
|
|
16
|
+
dependencies = [
|
|
17
|
+
"httpx>=0.24.0",
|
|
18
|
+
"pydantic>=2.0.0",
|
|
19
|
+
"pydantic-settings>=2.0.0",
|
|
20
|
+
"pycryptodome>=3.18.0",
|
|
21
|
+
"click>=8.0.0",
|
|
22
|
+
"httpcore>=0.17.0",
|
|
23
|
+
"prometheus-client>=0.23.1",
|
|
24
|
+
"fastapi>=0.123.0",
|
|
25
|
+
"opentelemetry-api>=1.38.0",
|
|
26
|
+
"opentelemetry-sdk>=1.38.0",
|
|
27
|
+
"opentelemetry-exporter-otlp-proto-http>=1.38.0",
|
|
28
|
+
"opentelemetry-instrumentation-fastapi>=0.59b0",
|
|
29
|
+
"opentelemetry-instrumentation-httpx>=0.59b0",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
dev = [
|
|
34
|
+
"pytest>=7.0.0",
|
|
35
|
+
"pytest-asyncio>=0.20.0"
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
[project.urls]
|
|
39
|
+
Homepage = "https://github.com/yantramai/guardianhub-sdk"
|
|
40
|
+
Source = "https://github.com/yantramai/guardianhub-sdk"
|
|
41
|
+
|
|
42
|
+
[tool.hatch.version]
|
|
43
|
+
path = "src/guardianhub/_version.py"
|
|
44
|
+
|
|
45
|
+
[tool.hatch.build.targets.wheel]
|
|
46
|
+
packages = ["src/guardianhub"]
|
|
47
|
+
|
|
48
|
+
[tool.hatch.build.targets.sdist]
|
|
49
|
+
include = ["src/guardianhub"]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# src/guardianhub/__init__.py
|
|
2
|
+
from ._version import __version__ # version exported for users
|
|
3
|
+
|
|
4
|
+
from .logging import get_logger,setup_logging
|
|
5
|
+
from .utils.app_state import AppState
|
|
6
|
+
from .utils.metrics import setup_metrics, get_metrics_registry
|
|
7
|
+
from .utils.fastapi_utils import setup_middleware, setup_health_endpoint, setup_metrics_endpoint
|
|
8
|
+
from .observability.instrumentation import configure_instrumentation
|
|
9
|
+
|
|
10
|
+
# Expose public modules (minimal)
|
|
11
|
+
__all__ = ["__version__",
|
|
12
|
+
"get_logger","setup_logging",
|
|
13
|
+
"AppState",
|
|
14
|
+
"setup_metrics","get_metrics_registry",
|
|
15
|
+
"setup_middleware","setup_health_endpoint","setup_metrics_endpoint",
|
|
16
|
+
"configure_instrumentation",
|
|
17
|
+
]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# guardianhub_sdk/agents/runtime.py
|
|
2
|
+
from typing import Any, Dict
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class AgentRuntime:
|
|
6
|
+
def __init__(self, clients: Dict[str, Any]):
|
|
7
|
+
self.clients = clients
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
async def run(self, spec: Dict[str, Any]):
|
|
11
|
+
# stub: execute agent spec using clients
|
|
12
|
+
return {"status": "ok", "spec": spec}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# guardianhub_sdk/auth/token_provider.py
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class TokenProvider:
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
"""Simple token provider stub. Replace with your vault/service account integration."""
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def __init__(self, api_key: Optional[str] = None):
|
|
12
|
+
self.api_key = api_key
|
|
13
|
+
self._cached_token = api_key
|
|
14
|
+
self._expires_at = None
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
async def get_token(self) -> str:
|
|
18
|
+
# For server-to-server use, you may want to implement JWT/service-account exchange
|
|
19
|
+
if self._cached_token:
|
|
20
|
+
return self._cached_token
|
|
21
|
+
# fallback placeholder
|
|
22
|
+
return "placeholder"
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Minio client with graceful error handling and connection management."""
|
|
2
|
+
import logging
|
|
3
|
+
from datetime import timedelta
|
|
4
|
+
from typing import Optional, Union, Dict, Any
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
|
|
8
|
+
from config import settings
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
class ClassificationClient:
|
|
12
|
+
"""Minio client wrapper with graceful error handling."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, base_url:str, poll_interval: int = 5, poll_timeout: int = 300):
|
|
15
|
+
self.initialized = False
|
|
16
|
+
self.client = None
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
self.api_url = base_url.rstrip('/')
|
|
20
|
+
self.headers = {
|
|
21
|
+
"Accept": "application/json",
|
|
22
|
+
}
|
|
23
|
+
self.poll_interval = poll_interval
|
|
24
|
+
self.poll_timeout = poll_timeout
|
|
25
|
+
|
|
26
|
+
# Initialize the persistent httpx client here.
|
|
27
|
+
# DO NOT use it in an 'async with' block in methods, or it will be closed.
|
|
28
|
+
self.client = httpx.AsyncClient(headers=self.headers, base_url=self.api_url,
|
|
29
|
+
timeout=self.poll_timeout + 60)
|
|
30
|
+
self.initialized = True
|
|
31
|
+
logger.info(f"✅ Classification client connected to {settings.endpoints.CLASSIFICATION_ENDPOINT}")
|
|
32
|
+
|
|
33
|
+
except Exception as e:
|
|
34
|
+
logger.warning(
|
|
35
|
+
f"⚠️ Failed to initialize Classification client: {str(e)}. "
|
|
36
|
+
"Classification operations will be disabled."
|
|
37
|
+
)
|
|
38
|
+
self.initialized = False
|
|
39
|
+
|
|
40
|
+
async def classify(self, text: str,metadata:Dict[str, Any]) -> Dict[str, Any]:
|
|
41
|
+
"""Generate a presigned URL for uploading an object."""
|
|
42
|
+
if not self.initialized:
|
|
43
|
+
logger.warning("Classification client not initialized, cannot generate presigned URL")
|
|
44
|
+
return {}
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
response = await self.client.post("/v1/document/classify", json={"text": text, "metadata":metadata})
|
|
48
|
+
return response.json()
|
|
49
|
+
|
|
50
|
+
except Exception as e:
|
|
51
|
+
logger.error(f"Failed to classify document: {str(e)}")
|
|
52
|
+
return {}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# services/clients/neo4j_client.py (New File/Class)
|
|
2
|
+
import datetime
|
|
3
|
+
import json
|
|
4
|
+
from typing import Dict, Any, Optional
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
import yaml # Must be imported for YAML output
|
|
8
|
+
# Import all model classes to make them available at the package level
|
|
9
|
+
from guardianhub.models.template.suggestion import TemplateSchemaSuggestion
|
|
10
|
+
|
|
11
|
+
from config import settings
|
|
12
|
+
from guardianhub_core import get_logger
|
|
13
|
+
logger = get_logger(__name__)
|
|
14
|
+
|
|
15
|
+
# NOTE: The actual driver must be injected/managed by the calling service (doc-template service)
|
|
16
|
+
class GraphDBClient:
|
|
17
|
+
def __init__(self, base_url: str, poll_interval: int = 5, poll_timeout: int = 300):
|
|
18
|
+
"""
|
|
19
|
+
Initializes the Paperless client.
|
|
20
|
+
"""
|
|
21
|
+
self.api_url = base_url.rstrip('/')
|
|
22
|
+
self.api_token = settings.endpoints.GRAPH_DB_URL
|
|
23
|
+
self.headers = {
|
|
24
|
+
"Accept": "application/json",
|
|
25
|
+
}
|
|
26
|
+
self.poll_interval = poll_interval
|
|
27
|
+
self.poll_timeout = poll_timeout
|
|
28
|
+
|
|
29
|
+
# Initialize the persistent httpx client here.
|
|
30
|
+
# DO NOT use it in an 'async with' block in methods, or it will be closed.
|
|
31
|
+
self.client = httpx.AsyncClient(headers=self.headers, base_url=self.api_url, timeout=self.poll_timeout + 60)
|
|
32
|
+
logger.info("PaperlessClient initialized for URL: %s", self.api_url)
|
|
33
|
+
|
|
34
|
+
async def save_document_template(self, template: TemplateSchemaSuggestion) -> bool:
|
|
35
|
+
"""
|
|
36
|
+
Creates a new DocumentTemplate node and links it to the Doc-Template Service
|
|
37
|
+
node by submitting a YAML payload to the ingestion endpoint.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
# 1. Prepare Node Properties
|
|
41
|
+
template_properties = {
|
|
42
|
+
"template_id": template.template_id,
|
|
43
|
+
"document_type": template.document_type,
|
|
44
|
+
"template_name": template.template_name,
|
|
45
|
+
"required_keywords": template.required_keywords,
|
|
46
|
+
# The ingestion endpoint requires the schema to be stored as a property value.
|
|
47
|
+
# We must serialize the JSON schema dictionary to a string/YAML-safe string.
|
|
48
|
+
"json_schema_str": json.dumps(template.json_schema)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# 2. Construct the Graph Ingestion Dictionary (The YAML Payload Structure)
|
|
52
|
+
ingestion_payload = {
|
|
53
|
+
"nodes": [
|
|
54
|
+
{
|
|
55
|
+
"type": "DocumentTemplate",
|
|
56
|
+
"properties": template_properties
|
|
57
|
+
}
|
|
58
|
+
],
|
|
59
|
+
"relationships": [
|
|
60
|
+
{
|
|
61
|
+
"from": {
|
|
62
|
+
"type": "PlatformService",
|
|
63
|
+
"property": "name",
|
|
64
|
+
"value": "doc-template-service" # Matching the service node created at startup
|
|
65
|
+
},
|
|
66
|
+
"to": {
|
|
67
|
+
"type": "DocumentTemplate",
|
|
68
|
+
"property": "template_id",
|
|
69
|
+
"value": template.template_id
|
|
70
|
+
},
|
|
71
|
+
"type": "MANAGES_TEMPLATE",
|
|
72
|
+
"properties": {
|
|
73
|
+
"link_date": datetime.datetime.now()
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
# 3. Convert to YAML
|
|
80
|
+
yaml_payload = yaml.dump(ingestion_payload, sort_keys=False)
|
|
81
|
+
|
|
82
|
+
# 4. POST to the ingestion endpoint
|
|
83
|
+
try:
|
|
84
|
+
response = await self.client.post(
|
|
85
|
+
"/ingest-yaml-schema",
|
|
86
|
+
content=yaml_payload,
|
|
87
|
+
headers={'Content-Type': 'application/x-yaml'},
|
|
88
|
+
timeout=30.0
|
|
89
|
+
)
|
|
90
|
+
response.raise_for_status()
|
|
91
|
+
|
|
92
|
+
response_json = response.json()
|
|
93
|
+
if response_json.get("status") == "success":
|
|
94
|
+
logger.info(f"✅ Template {template.template_id} successfully persisted via YAML ingestion.")
|
|
95
|
+
return True
|
|
96
|
+
else:
|
|
97
|
+
logger.error(
|
|
98
|
+
f"Graph DB Service returned non-success status for template {template.template_id}: {response_json.get('message')}")
|
|
99
|
+
return False
|
|
100
|
+
|
|
101
|
+
except httpx.HTTPStatusError as e:
|
|
102
|
+
logger.error(
|
|
103
|
+
f"HTTP error during YAML ingestion (Template {template.template_id}). Status: {e.response.status_code}. Detail: {e.response.text}",
|
|
104
|
+
exc_info=True
|
|
105
|
+
)
|
|
106
|
+
return False
|
|
107
|
+
except Exception as e:
|
|
108
|
+
logger.error(f"Failed to ingest template YAML for {template.template_id}: {e}", exc_info=True)
|
|
109
|
+
return False
|
|
110
|
+
|
|
111
|
+
# services/clients/neo4j_client.py (Corrected get_template_by_id)
|
|
112
|
+
|
|
113
|
+
async def get_template_by_id(self, template_id: str) -> Optional[Dict[str, Any]]:
|
|
114
|
+
"""
|
|
115
|
+
Retrieves the properties of a DocumentTemplate node by its ID via the
|
|
116
|
+
/query-cypher endpoint.
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
# 1. Define the Cypher query
|
|
120
|
+
cypher_query = """
|
|
121
|
+
MATCH (t:DocumentTemplate {template_id: $template_id})
|
|
122
|
+
RETURN properties(t) as template
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
# 2. Prepare the JSON payload for the read endpoint
|
|
126
|
+
payload = {
|
|
127
|
+
"query": cypher_query,
|
|
128
|
+
"parameters": {"template_id": template_id}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
# 🛠️ FIX: Target the correct read endpoint and send JSON payload
|
|
133
|
+
response = await self.client.post(
|
|
134
|
+
"/query-cypher", # The correct read endpoint
|
|
135
|
+
json=payload, # Send as JSON
|
|
136
|
+
timeout=30.0
|
|
137
|
+
)
|
|
138
|
+
response.raise_for_status()
|
|
139
|
+
|
|
140
|
+
response_json = response.json()
|
|
141
|
+
|
|
142
|
+
if response_json.get("status") == "success" and response_json.get("results"):
|
|
143
|
+
# The result is typically a list of dicts from Cypher execution
|
|
144
|
+
record = response_json["results"][0]
|
|
145
|
+
template_data = record["template"]
|
|
146
|
+
|
|
147
|
+
# Convert the stored JSON Schema string back to a dictionary
|
|
148
|
+
template_data['json_schema'] = json.loads(template_data.pop('json_schema_str'))
|
|
149
|
+
|
|
150
|
+
logger.info(f"✅ Template {template_id} successfully retrieved from GraphDB.")
|
|
151
|
+
return template_data
|
|
152
|
+
else:
|
|
153
|
+
logger.info(f"Template {template_id} not found or query failed: {response_json.get('message')}")
|
|
154
|
+
return None
|
|
155
|
+
|
|
156
|
+
except httpx.HTTPStatusError as e:
|
|
157
|
+
logger.error(f"HTTP error retrieving template {template_id}. Status: {e.response.status_code}")
|
|
158
|
+
return None
|
|
159
|
+
except Exception as e:
|
|
160
|
+
logger.error(f"Failed to retrieve template {template_id} from Neo4j: {e}", exc_info=True)
|
|
161
|
+
return None
|