rulhub 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.
- rulhub-0.1.0/.gitignore +88 -0
- rulhub-0.1.0/PKG-INFO +116 -0
- rulhub-0.1.0/README.md +88 -0
- rulhub-0.1.0/icc_rule_engine/__init__.py +22 -0
- rulhub-0.1.0/icc_rule_engine/client.py +365 -0
- rulhub-0.1.0/icc_rule_engine/exceptions.py +42 -0
- rulhub-0.1.0/icc_rule_engine/models.py +92 -0
- rulhub-0.1.0/py.typed +0 -0
- rulhub-0.1.0/pyproject.toml +42 -0
- rulhub-0.1.0/requirements.txt +3 -0
- rulhub-0.1.0/rulehub_client.py +378 -0
- rulhub-0.1.0/rulhub/__init__.py +6 -0
- rulhub-0.1.0/rulhub/client.py +234 -0
- rulhub-0.1.0/rulhub/py.typed +0 -0
- rulhub-0.1.0/setup.py +47 -0
rulhub-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# Virtual environment
|
|
7
|
+
venv/
|
|
8
|
+
.venv/
|
|
9
|
+
|
|
10
|
+
# Environment files (secrets)
|
|
11
|
+
/.env
|
|
12
|
+
.env
|
|
13
|
+
.env.local
|
|
14
|
+
.env.production
|
|
15
|
+
.env.staging
|
|
16
|
+
.env.development
|
|
17
|
+
.env.test
|
|
18
|
+
*.env
|
|
19
|
+
.env.*
|
|
20
|
+
!.env.example
|
|
21
|
+
|
|
22
|
+
# IDE settings
|
|
23
|
+
.vscode/
|
|
24
|
+
.idea/
|
|
25
|
+
|
|
26
|
+
# Migrations
|
|
27
|
+
alembic/versions/*.pyc
|
|
28
|
+
alembic/__pycache__/
|
|
29
|
+
|
|
30
|
+
# Logs
|
|
31
|
+
*.log
|
|
32
|
+
|
|
33
|
+
# OS junk
|
|
34
|
+
.DS_Store
|
|
35
|
+
Thumbs.db
|
|
36
|
+
|
|
37
|
+
# Security files
|
|
38
|
+
*.pem
|
|
39
|
+
*.key
|
|
40
|
+
*.crt
|
|
41
|
+
*.p12
|
|
42
|
+
*.pfx
|
|
43
|
+
secrets/
|
|
44
|
+
credentials/
|
|
45
|
+
.secrets
|
|
46
|
+
api_keys.txt
|
|
47
|
+
.password
|
|
48
|
+
.token
|
|
49
|
+
|
|
50
|
+
# Database files (may contain sensitive data)
|
|
51
|
+
*.db-journal
|
|
52
|
+
*.sqlite-journal
|
|
53
|
+
|
|
54
|
+
# Backup files (may contain secrets)
|
|
55
|
+
*.bak
|
|
56
|
+
*.backup
|
|
57
|
+
*.old
|
|
58
|
+
*~
|
|
59
|
+
|
|
60
|
+
# Docker secrets
|
|
61
|
+
.docker/
|
|
62
|
+
docker-compose.override.yml
|
|
63
|
+
|
|
64
|
+
# Temporary files
|
|
65
|
+
temp/
|
|
66
|
+
tmp/
|
|
67
|
+
.tmp/
|
|
68
|
+
|
|
69
|
+
# Editor temp files
|
|
70
|
+
.swp
|
|
71
|
+
.swo
|
|
72
|
+
*~
|
|
73
|
+
|
|
74
|
+
# Node modules
|
|
75
|
+
node_modules/
|
|
76
|
+
admin/node_modules/
|
|
77
|
+
landing/node_modules/
|
|
78
|
+
|
|
79
|
+
# Problematic Windows device filename
|
|
80
|
+
/nul
|
|
81
|
+
|
|
82
|
+
# Playwright artifacts
|
|
83
|
+
playwright-report/
|
|
84
|
+
test-results/
|
|
85
|
+
admin/playwright-report/
|
|
86
|
+
admin/test-results/
|
|
87
|
+
|
|
88
|
+
.vercel
|
rulhub-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rulhub
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: RulHub SDK — Trade finance compliance API client
|
|
5
|
+
Project-URL: Homepage, https://rulhub.com
|
|
6
|
+
Project-URL: Documentation, https://api.rulhub.com/swagger
|
|
7
|
+
Project-URL: Repository, https://github.com/ripclass/RulEngine
|
|
8
|
+
Project-URL: Issues, https://github.com/ripclass/RulEngine/issues
|
|
9
|
+
Author-email: Enso Intelligence Labs <api@rulhub.com>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
Keywords: api,compliance,icc,letter-of-credit,sanctions,trade-finance,ucp600
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Office/Business :: Financial
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.8
|
|
26
|
+
Requires-Dist: requests>=2.28.0
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# RulHub Python SDK
|
|
30
|
+
|
|
31
|
+
Trade finance compliance API — validate documents against 5,400+ ICC rules with AI-powered semantic matching.
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install rulhub
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Quick Start
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from rulhub import RulHub
|
|
43
|
+
|
|
44
|
+
client = RulHub(api_key="ak_your_key_here")
|
|
45
|
+
|
|
46
|
+
# Validate a letter of credit
|
|
47
|
+
result = client.validate(
|
|
48
|
+
rules="ucp600",
|
|
49
|
+
document_type="lc",
|
|
50
|
+
document={
|
|
51
|
+
"beneficiary_name": "ABC Trading Co",
|
|
52
|
+
"amount": 50000,
|
|
53
|
+
"currency": "USD",
|
|
54
|
+
"goods_description": "1000 MT Thai White Rice 5% Broken",
|
|
55
|
+
"expiry_date": "2026-06-30",
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
print(f"Score: {result['score']:.0%}")
|
|
60
|
+
print(f"Compliant: {result['compliant']}")
|
|
61
|
+
|
|
62
|
+
for d in result["discrepancies"]:
|
|
63
|
+
print(f" [{d['severity']}] {d['finding']}")
|
|
64
|
+
if d.get("recommendation"):
|
|
65
|
+
print(f" Fix: {d['recommendation']}")
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Validate Multiple Documents
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
result = client.validate_set(documents=[
|
|
72
|
+
{"type": "lc", "fields": {"amount": 50000, "currency": "USD"}},
|
|
73
|
+
{"type": "invoice", "fields": {"total_amount": 49500, "currency": "USD"}},
|
|
74
|
+
])
|
|
75
|
+
|
|
76
|
+
for d in result["cross_document_discrepancies"]:
|
|
77
|
+
print(f" {d['finding']} ({', '.join(d['documents_involved'])})")
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Sanctions Screening
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
result = client.screen_sanctions(entity="Acme Corp", country="IR")
|
|
84
|
+
print(f"Clear: {result['clear']}, Risk: {result['risk_level']}")
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## All Methods
|
|
88
|
+
|
|
89
|
+
| Method | Description |
|
|
90
|
+
|--------|-------------|
|
|
91
|
+
| `validate()` | Single document validation |
|
|
92
|
+
| `validate_set()` | Multi-document cross-doc validation |
|
|
93
|
+
| `screen_sanctions()` | OFAC/EU/UN/UK sanctions screening |
|
|
94
|
+
| `screen_entity()` | UBO/PEP/shell company risk |
|
|
95
|
+
| `screen_export_controls()` | EAR/ITAR/EU dual-use |
|
|
96
|
+
| `screen_tbml()` | FATF money laundering indicators |
|
|
97
|
+
| `screen_route()` | Route sanctions + transport |
|
|
98
|
+
| `search_rules()` | Keyword search across all rules |
|
|
99
|
+
| `lookup_rules()` | Structured rule retrieval |
|
|
100
|
+
| `get_requirements()` | Document field requirements |
|
|
101
|
+
| `get_bank_profiles()` | Bank behavior data |
|
|
102
|
+
| `get_country_requirements()` | Country compliance rules |
|
|
103
|
+
| `get_fta_origin()` | FTA eligibility rules |
|
|
104
|
+
| `get_commodities()` | Commodity compliance |
|
|
105
|
+
|
|
106
|
+
## Sign Up
|
|
107
|
+
|
|
108
|
+
Free plan: 50 validations/month. No credit card.
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
curl -X POST https://api.rulhub.com/v1/signup \
|
|
112
|
+
-H "Content-Type: application/json" \
|
|
113
|
+
-d '{"email":"you@co.com","password":"secure123","company_name":"Your Co"}'
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**API Docs:** https://api.rulhub.com/swagger | **Website:** https://rulhub.com | **By:** Enso Intelligence Labs, Inc.
|
rulhub-0.1.0/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# RulHub Python SDK
|
|
2
|
+
|
|
3
|
+
Trade finance compliance API — validate documents against 5,400+ ICC rules with AI-powered semantic matching.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install rulhub
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from rulhub import RulHub
|
|
15
|
+
|
|
16
|
+
client = RulHub(api_key="ak_your_key_here")
|
|
17
|
+
|
|
18
|
+
# Validate a letter of credit
|
|
19
|
+
result = client.validate(
|
|
20
|
+
rules="ucp600",
|
|
21
|
+
document_type="lc",
|
|
22
|
+
document={
|
|
23
|
+
"beneficiary_name": "ABC Trading Co",
|
|
24
|
+
"amount": 50000,
|
|
25
|
+
"currency": "USD",
|
|
26
|
+
"goods_description": "1000 MT Thai White Rice 5% Broken",
|
|
27
|
+
"expiry_date": "2026-06-30",
|
|
28
|
+
}
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
print(f"Score: {result['score']:.0%}")
|
|
32
|
+
print(f"Compliant: {result['compliant']}")
|
|
33
|
+
|
|
34
|
+
for d in result["discrepancies"]:
|
|
35
|
+
print(f" [{d['severity']}] {d['finding']}")
|
|
36
|
+
if d.get("recommendation"):
|
|
37
|
+
print(f" Fix: {d['recommendation']}")
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Validate Multiple Documents
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
result = client.validate_set(documents=[
|
|
44
|
+
{"type": "lc", "fields": {"amount": 50000, "currency": "USD"}},
|
|
45
|
+
{"type": "invoice", "fields": {"total_amount": 49500, "currency": "USD"}},
|
|
46
|
+
])
|
|
47
|
+
|
|
48
|
+
for d in result["cross_document_discrepancies"]:
|
|
49
|
+
print(f" {d['finding']} ({', '.join(d['documents_involved'])})")
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Sanctions Screening
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
result = client.screen_sanctions(entity="Acme Corp", country="IR")
|
|
56
|
+
print(f"Clear: {result['clear']}, Risk: {result['risk_level']}")
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## All Methods
|
|
60
|
+
|
|
61
|
+
| Method | Description |
|
|
62
|
+
|--------|-------------|
|
|
63
|
+
| `validate()` | Single document validation |
|
|
64
|
+
| `validate_set()` | Multi-document cross-doc validation |
|
|
65
|
+
| `screen_sanctions()` | OFAC/EU/UN/UK sanctions screening |
|
|
66
|
+
| `screen_entity()` | UBO/PEP/shell company risk |
|
|
67
|
+
| `screen_export_controls()` | EAR/ITAR/EU dual-use |
|
|
68
|
+
| `screen_tbml()` | FATF money laundering indicators |
|
|
69
|
+
| `screen_route()` | Route sanctions + transport |
|
|
70
|
+
| `search_rules()` | Keyword search across all rules |
|
|
71
|
+
| `lookup_rules()` | Structured rule retrieval |
|
|
72
|
+
| `get_requirements()` | Document field requirements |
|
|
73
|
+
| `get_bank_profiles()` | Bank behavior data |
|
|
74
|
+
| `get_country_requirements()` | Country compliance rules |
|
|
75
|
+
| `get_fta_origin()` | FTA eligibility rules |
|
|
76
|
+
| `get_commodities()` | Commodity compliance |
|
|
77
|
+
|
|
78
|
+
## Sign Up
|
|
79
|
+
|
|
80
|
+
Free plan: 50 validations/month. No credit card.
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
curl -X POST https://api.rulhub.com/v1/signup \
|
|
84
|
+
-H "Content-Type: application/json" \
|
|
85
|
+
-d '{"email":"you@co.com","password":"secure123","company_name":"Your Co"}'
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**API Docs:** https://api.rulhub.com/swagger | **Website:** https://rulhub.com | **By:** Enso Intelligence Labs, Inc.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ICC Rule Engine Python SDK
|
|
3
|
+
|
|
4
|
+
A comprehensive Python SDK for interacting with the ICC Rule Engine API.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .client import ICCRuleEngineClient
|
|
8
|
+
from .models import Rule, ValidationRequest, ValidationResult, RuleCondition, ExpectedOutcome
|
|
9
|
+
from .exceptions import ICCRuleEngineError, ValidationError, APIError
|
|
10
|
+
|
|
11
|
+
__version__ = "1.0.0"
|
|
12
|
+
__all__ = [
|
|
13
|
+
"ICCRuleEngineClient",
|
|
14
|
+
"Rule",
|
|
15
|
+
"ValidationRequest",
|
|
16
|
+
"ValidationResult",
|
|
17
|
+
"RuleCondition",
|
|
18
|
+
"ExpectedOutcome",
|
|
19
|
+
"ICCRuleEngineError",
|
|
20
|
+
"ValidationError",
|
|
21
|
+
"APIError"
|
|
22
|
+
]
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ICC Rule Engine Python SDK Client
|
|
3
|
+
"""
|
|
4
|
+
import asyncio
|
|
5
|
+
from typing import Any, Dict, List, Optional, Union
|
|
6
|
+
from urllib.parse import urljoin
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
from pydantic import ValidationError as PydanticValidationError
|
|
10
|
+
|
|
11
|
+
from .models import (
|
|
12
|
+
Rule,
|
|
13
|
+
ValidationRequest,
|
|
14
|
+
ValidationResult,
|
|
15
|
+
BatchValidationRequest,
|
|
16
|
+
BatchValidationResult,
|
|
17
|
+
HealthStatus,
|
|
18
|
+
MetricsResponse
|
|
19
|
+
)
|
|
20
|
+
from .exceptions import (
|
|
21
|
+
APIError,
|
|
22
|
+
AuthenticationError,
|
|
23
|
+
NetworkError,
|
|
24
|
+
RateLimitError,
|
|
25
|
+
TimeoutError,
|
|
26
|
+
ValidationError
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ICCRuleEngineClient:
|
|
31
|
+
"""
|
|
32
|
+
ICC Rule Engine API Client
|
|
33
|
+
|
|
34
|
+
A comprehensive client for interacting with the ICC Rule Engine API.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
base_url: str,
|
|
40
|
+
api_key: str,
|
|
41
|
+
timeout: float = 30.0,
|
|
42
|
+
max_retries: int = 3,
|
|
43
|
+
retry_delay: float = 1.0
|
|
44
|
+
):
|
|
45
|
+
"""
|
|
46
|
+
Initialize the ICC Rule Engine client
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
base_url: Base URL of the ICC Rule Engine API
|
|
50
|
+
api_key: API key for authentication
|
|
51
|
+
timeout: Request timeout in seconds
|
|
52
|
+
max_retries: Maximum number of retries for failed requests
|
|
53
|
+
retry_delay: Delay between retries in seconds
|
|
54
|
+
"""
|
|
55
|
+
self.base_url = base_url.rstrip("/")
|
|
56
|
+
self.api_key = api_key
|
|
57
|
+
self.timeout = timeout
|
|
58
|
+
self.max_retries = max_retries
|
|
59
|
+
self.retry_delay = retry_delay
|
|
60
|
+
|
|
61
|
+
self._client = httpx.Client(
|
|
62
|
+
timeout=timeout,
|
|
63
|
+
headers={
|
|
64
|
+
"X-API-Key": api_key,
|
|
65
|
+
"Content-Type": "application/json",
|
|
66
|
+
"User-Agent": "ICC-Rule-Engine-SDK/1.0.0"
|
|
67
|
+
}
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
def __enter__(self):
|
|
71
|
+
return self
|
|
72
|
+
|
|
73
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
74
|
+
self.close()
|
|
75
|
+
|
|
76
|
+
def close(self):
|
|
77
|
+
"""Close the HTTP client"""
|
|
78
|
+
self._client.close()
|
|
79
|
+
|
|
80
|
+
def _make_request(
|
|
81
|
+
self,
|
|
82
|
+
method: str,
|
|
83
|
+
endpoint: str,
|
|
84
|
+
json_data: Optional[Dict[str, Any]] = None,
|
|
85
|
+
params: Optional[Dict[str, Any]] = None
|
|
86
|
+
) -> Dict[str, Any]:
|
|
87
|
+
"""Make HTTP request with error handling and retries"""
|
|
88
|
+
|
|
89
|
+
url = urljoin(self.base_url + "/", endpoint.lstrip("/"))
|
|
90
|
+
|
|
91
|
+
for attempt in range(self.max_retries + 1):
|
|
92
|
+
try:
|
|
93
|
+
response = self._client.request(
|
|
94
|
+
method=method,
|
|
95
|
+
url=url,
|
|
96
|
+
json=json_data,
|
|
97
|
+
params=params
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
if response.status_code == 401:
|
|
101
|
+
raise AuthenticationError("Invalid API key")
|
|
102
|
+
elif response.status_code == 429:
|
|
103
|
+
raise RateLimitError("Rate limit exceeded")
|
|
104
|
+
elif response.status_code >= 400:
|
|
105
|
+
error_data = {}
|
|
106
|
+
try:
|
|
107
|
+
error_data = response.json()
|
|
108
|
+
except Exception:
|
|
109
|
+
pass
|
|
110
|
+
|
|
111
|
+
raise APIError(
|
|
112
|
+
message=error_data.get("detail", f"API error: {response.status_code}"),
|
|
113
|
+
status_code=response.status_code,
|
|
114
|
+
response_data=error_data
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
return response.json()
|
|
118
|
+
|
|
119
|
+
except httpx.TimeoutException:
|
|
120
|
+
if attempt == self.max_retries:
|
|
121
|
+
raise TimeoutError("Request timed out")
|
|
122
|
+
except httpx.NetworkError as e:
|
|
123
|
+
if attempt == self.max_retries:
|
|
124
|
+
raise NetworkError(f"Network error: {e}")
|
|
125
|
+
except (APIError, AuthenticationError, RateLimitError):
|
|
126
|
+
raise
|
|
127
|
+
|
|
128
|
+
if attempt < self.max_retries:
|
|
129
|
+
import time
|
|
130
|
+
time.sleep(self.retry_delay * (2 ** attempt))
|
|
131
|
+
|
|
132
|
+
# Health and Status Methods
|
|
133
|
+
|
|
134
|
+
def health_check(self) -> HealthStatus:
|
|
135
|
+
"""Check API health status"""
|
|
136
|
+
data = self._make_request("GET", "/health")
|
|
137
|
+
return HealthStatus(**data)
|
|
138
|
+
|
|
139
|
+
def get_metrics(self) -> MetricsResponse:
|
|
140
|
+
"""Get API metrics"""
|
|
141
|
+
data = self._make_request("GET", "/metrics/")
|
|
142
|
+
return MetricsResponse(**data)
|
|
143
|
+
|
|
144
|
+
# Rule Management Methods
|
|
145
|
+
|
|
146
|
+
def create_rule(self, rule: Union[Rule, Dict[str, Any]]) -> Rule:
|
|
147
|
+
"""Create a new rule"""
|
|
148
|
+
if isinstance(rule, Rule):
|
|
149
|
+
rule_data = rule.model_dump(exclude_unset=True)
|
|
150
|
+
else:
|
|
151
|
+
rule_data = rule
|
|
152
|
+
|
|
153
|
+
data = self._make_request("POST", "/rules/", json_data=rule_data)
|
|
154
|
+
return Rule(**data)
|
|
155
|
+
|
|
156
|
+
def get_rule(self, rule_id: str) -> Rule:
|
|
157
|
+
"""Get rule by ID"""
|
|
158
|
+
data = self._make_request("GET", f"/rules/{rule_id}")
|
|
159
|
+
return Rule(**data)
|
|
160
|
+
|
|
161
|
+
def update_rule(self, rule_id: str, rule: Union[Rule, Dict[str, Any]]) -> Rule:
|
|
162
|
+
"""Update an existing rule"""
|
|
163
|
+
if isinstance(rule, Rule):
|
|
164
|
+
rule_data = rule.model_dump(exclude_unset=True)
|
|
165
|
+
else:
|
|
166
|
+
rule_data = rule
|
|
167
|
+
|
|
168
|
+
data = self._make_request("PUT", f"/rules/{rule_id}", json_data=rule_data)
|
|
169
|
+
return Rule(**data)
|
|
170
|
+
|
|
171
|
+
def delete_rule(self, rule_id: str) -> Dict[str, str]:
|
|
172
|
+
"""Delete a rule"""
|
|
173
|
+
return self._make_request("DELETE", f"/rules/{rule_id}")
|
|
174
|
+
|
|
175
|
+
def list_rules(
|
|
176
|
+
self,
|
|
177
|
+
source: Optional[str] = None,
|
|
178
|
+
article: Optional[str] = None,
|
|
179
|
+
tags: Optional[List[str]] = None,
|
|
180
|
+
severity: Optional[str] = None,
|
|
181
|
+
active: Optional[bool] = None,
|
|
182
|
+
limit: Optional[int] = None,
|
|
183
|
+
skip: Optional[int] = None
|
|
184
|
+
) -> List[Rule]:
|
|
185
|
+
"""List rules with optional filters"""
|
|
186
|
+
params = {}
|
|
187
|
+
if source:
|
|
188
|
+
params["source"] = source
|
|
189
|
+
if article:
|
|
190
|
+
params["article"] = article
|
|
191
|
+
if tags:
|
|
192
|
+
params["tags"] = ",".join(tags)
|
|
193
|
+
if severity:
|
|
194
|
+
params["severity"] = severity
|
|
195
|
+
if active is not None:
|
|
196
|
+
params["active"] = active
|
|
197
|
+
if limit:
|
|
198
|
+
params["limit"] = limit
|
|
199
|
+
if skip:
|
|
200
|
+
params["skip"] = skip
|
|
201
|
+
|
|
202
|
+
data = self._make_request("GET", "/rules/", params=params)
|
|
203
|
+
return [Rule(**rule) for rule in data]
|
|
204
|
+
|
|
205
|
+
# Validation Methods
|
|
206
|
+
|
|
207
|
+
def validate_document(
|
|
208
|
+
self,
|
|
209
|
+
document: Dict[str, Any],
|
|
210
|
+
ruleset: Dict[str, Any]
|
|
211
|
+
) -> ValidationResult:
|
|
212
|
+
"""Validate a single document"""
|
|
213
|
+
request_data = ValidationRequest(document=document, ruleset=ruleset)
|
|
214
|
+
data = self._make_request("POST", "/validate/", json_data=request_data.model_dump())
|
|
215
|
+
return ValidationResult(**data)
|
|
216
|
+
|
|
217
|
+
def validate_batch(
|
|
218
|
+
self,
|
|
219
|
+
documents: List[Dict[str, Any]],
|
|
220
|
+
ruleset: Dict[str, Any],
|
|
221
|
+
parallel: bool = True
|
|
222
|
+
) -> BatchValidationResult:
|
|
223
|
+
"""Validate multiple documents"""
|
|
224
|
+
request_data = BatchValidationRequest(
|
|
225
|
+
documents=documents,
|
|
226
|
+
ruleset=ruleset,
|
|
227
|
+
parallel=parallel
|
|
228
|
+
)
|
|
229
|
+
data = self._make_request("POST", "/validate/batch", json_data=request_data.model_dump())
|
|
230
|
+
return BatchValidationResult(**data)
|
|
231
|
+
|
|
232
|
+
# Convenience Methods
|
|
233
|
+
|
|
234
|
+
def validate_letter_of_credit(
|
|
235
|
+
self,
|
|
236
|
+
document: Dict[str, Any],
|
|
237
|
+
source: str = "UCP600",
|
|
238
|
+
article: Optional[str] = None
|
|
239
|
+
) -> ValidationResult:
|
|
240
|
+
"""Convenience method for validating letters of credit"""
|
|
241
|
+
ruleset = {"source": source}
|
|
242
|
+
if article:
|
|
243
|
+
ruleset["article"] = article
|
|
244
|
+
|
|
245
|
+
return self.validate_document(document, ruleset)
|
|
246
|
+
|
|
247
|
+
def get_rules_by_source(self, source: str) -> List[Rule]:
|
|
248
|
+
"""Get all rules for a specific source"""
|
|
249
|
+
return self.list_rules(source=source)
|
|
250
|
+
|
|
251
|
+
def get_active_rules(self) -> List[Rule]:
|
|
252
|
+
"""Get all active rules"""
|
|
253
|
+
return self.list_rules(active=True)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
class AsyncICCRuleEngineClient:
|
|
257
|
+
"""
|
|
258
|
+
Async ICC Rule Engine API Client
|
|
259
|
+
|
|
260
|
+
An async version of the ICC Rule Engine client for high-performance applications.
|
|
261
|
+
"""
|
|
262
|
+
|
|
263
|
+
def __init__(
|
|
264
|
+
self,
|
|
265
|
+
base_url: str,
|
|
266
|
+
api_key: str,
|
|
267
|
+
timeout: float = 30.0,
|
|
268
|
+
max_retries: int = 3,
|
|
269
|
+
retry_delay: float = 1.0
|
|
270
|
+
):
|
|
271
|
+
"""
|
|
272
|
+
Initialize the async ICC Rule Engine client
|
|
273
|
+
|
|
274
|
+
Args:
|
|
275
|
+
base_url: Base URL of the ICC Rule Engine API
|
|
276
|
+
api_key: API key for authentication
|
|
277
|
+
timeout: Request timeout in seconds
|
|
278
|
+
max_retries: Maximum number of retries for failed requests
|
|
279
|
+
retry_delay: Delay between retries in seconds
|
|
280
|
+
"""
|
|
281
|
+
self.base_url = base_url.rstrip("/")
|
|
282
|
+
self.api_key = api_key
|
|
283
|
+
self.timeout = timeout
|
|
284
|
+
self.max_retries = max_retries
|
|
285
|
+
self.retry_delay = retry_delay
|
|
286
|
+
|
|
287
|
+
self._client = httpx.AsyncClient(
|
|
288
|
+
timeout=timeout,
|
|
289
|
+
headers={
|
|
290
|
+
"X-API-Key": api_key,
|
|
291
|
+
"Content-Type": "application/json",
|
|
292
|
+
"User-Agent": "ICC-Rule-Engine-SDK/1.0.0"
|
|
293
|
+
}
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
async def __aenter__(self):
|
|
297
|
+
return self
|
|
298
|
+
|
|
299
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
300
|
+
await self.aclose()
|
|
301
|
+
|
|
302
|
+
async def aclose(self):
|
|
303
|
+
"""Close the HTTP client"""
|
|
304
|
+
await self._client.aclose()
|
|
305
|
+
|
|
306
|
+
async def _make_request(
|
|
307
|
+
self,
|
|
308
|
+
method: str,
|
|
309
|
+
endpoint: str,
|
|
310
|
+
json_data: Optional[Dict[str, Any]] = None,
|
|
311
|
+
params: Optional[Dict[str, Any]] = None
|
|
312
|
+
) -> Dict[str, Any]:
|
|
313
|
+
"""Make async HTTP request with error handling and retries"""
|
|
314
|
+
|
|
315
|
+
url = urljoin(self.base_url + "/", endpoint.lstrip("/"))
|
|
316
|
+
|
|
317
|
+
for attempt in range(self.max_retries + 1):
|
|
318
|
+
try:
|
|
319
|
+
response = await self._client.request(
|
|
320
|
+
method=method,
|
|
321
|
+
url=url,
|
|
322
|
+
json=json_data,
|
|
323
|
+
params=params
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
if response.status_code == 401:
|
|
327
|
+
raise AuthenticationError("Invalid API key")
|
|
328
|
+
elif response.status_code == 429:
|
|
329
|
+
raise RateLimitError("Rate limit exceeded")
|
|
330
|
+
elif response.status_code >= 400:
|
|
331
|
+
error_data = {}
|
|
332
|
+
try:
|
|
333
|
+
error_data = response.json()
|
|
334
|
+
except Exception:
|
|
335
|
+
pass
|
|
336
|
+
|
|
337
|
+
raise APIError(
|
|
338
|
+
message=error_data.get("detail", f"API error: {response.status_code}"),
|
|
339
|
+
status_code=response.status_code,
|
|
340
|
+
response_data=error_data
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
return response.json()
|
|
344
|
+
|
|
345
|
+
except httpx.TimeoutException:
|
|
346
|
+
if attempt == self.max_retries:
|
|
347
|
+
raise TimeoutError("Request timed out")
|
|
348
|
+
except httpx.NetworkError as e:
|
|
349
|
+
if attempt == self.max_retries:
|
|
350
|
+
raise NetworkError(f"Network error: {e}")
|
|
351
|
+
except (APIError, AuthenticationError, RateLimitError):
|
|
352
|
+
raise
|
|
353
|
+
|
|
354
|
+
if attempt < self.max_retries:
|
|
355
|
+
await asyncio.sleep(self.retry_delay * (2 ** attempt))
|
|
356
|
+
|
|
357
|
+
async def validate_document(
|
|
358
|
+
self,
|
|
359
|
+
document: Dict[str, Any],
|
|
360
|
+
ruleset: Dict[str, Any]
|
|
361
|
+
) -> ValidationResult:
|
|
362
|
+
"""Validate a single document (async)"""
|
|
363
|
+
request_data = ValidationRequest(document=document, ruleset=ruleset)
|
|
364
|
+
data = await self._make_request("POST", "/validate/", json_data=request_data.model_dump())
|
|
365
|
+
return ValidationResult(**data)
|