Fix Cove importer: correct API payload format and response parsing
- Login: use lowercase username/password params and id="jsonrpc"
- Login: visa is at top-level of response (not inside result)
- EnumerateAccountStatistics: use lowercase query param, RecordsCount
instead of RecordCount, remove DisplayColumns (not needed)
- _flatten_settings: Settings items are single-key dicts like
{"D09F00": "5"}, not {Key: ..., Value: ...} - use dict.update()
- _cove_enumerate: unwrap nested result and handle Accounts key
- _process_account: AccountId is top-level field, not from Settings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
467f350184
commit
3bd8178464
@ -76,15 +76,20 @@ def _cove_login(url: str, username: str, password: str) -> tuple[str, int]:
|
|||||||
"""
|
"""
|
||||||
payload = {
|
payload = {
|
||||||
"jsonrpc": "2.0",
|
"jsonrpc": "2.0",
|
||||||
|
"id": "jsonrpc",
|
||||||
"method": "Login",
|
"method": "Login",
|
||||||
"id": 1,
|
|
||||||
"params": {
|
"params": {
|
||||||
"Username": username,
|
"username": username,
|
||||||
"Password": password,
|
"password": password,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
resp = requests.post(url, json=payload, timeout=30)
|
resp = requests.post(
|
||||||
|
url,
|
||||||
|
json=payload,
|
||||||
|
headers={"Content-Type": "application/json"},
|
||||||
|
timeout=30,
|
||||||
|
)
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
data = resp.json()
|
data = resp.json()
|
||||||
except requests.RequestException as exc:
|
except requests.RequestException as exc:
|
||||||
@ -92,21 +97,22 @@ def _cove_login(url: str, username: str, password: str) -> tuple[str, int]:
|
|||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise CoveImportError(f"Cove login response is not valid JSON: {exc}") from exc
|
raise CoveImportError(f"Cove login response is not valid JSON: {exc}") from exc
|
||||||
|
|
||||||
result = data.get("result") or {}
|
if "error" in data and data["error"]:
|
||||||
if not result:
|
error = data["error"]
|
||||||
error = data.get("error") or {}
|
msg = error.get("message") or str(error) if isinstance(error, dict) else str(error)
|
||||||
raise CoveImportError(f"Cove login failed: {error.get('message', 'unknown error')}")
|
raise CoveImportError(f"Cove login failed: {msg}")
|
||||||
|
|
||||||
visa = result.get("Visa") or ""
|
# Visa is returned at the top level of the response (not inside result)
|
||||||
|
visa = data.get("visa") or ""
|
||||||
if not visa:
|
if not visa:
|
||||||
raise CoveImportError("Cove login succeeded but no Visa token returned")
|
raise CoveImportError("Cove login succeeded but no visa token returned")
|
||||||
|
|
||||||
# Partner/account info may be nested
|
# PartnerId is inside result
|
||||||
account_info = result.get("Accounts", [{}])[0] if result.get("Accounts") else {}
|
result = data.get("result") or {}
|
||||||
partner_id = (
|
partner_id = (
|
||||||
account_info.get("PartnerId")
|
result.get("PartnerId")
|
||||||
or result.get("PartnerID")
|
or result.get("PartnerID")
|
||||||
or result.get("PartnerId")
|
or result.get("result", {}).get("PartnerId")
|
||||||
or 0
|
or 0
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -126,21 +132,25 @@ def _cove_enumerate(
|
|||||||
"""
|
"""
|
||||||
payload = {
|
payload = {
|
||||||
"jsonrpc": "2.0",
|
"jsonrpc": "2.0",
|
||||||
"method": "EnumerateAccountStatistics",
|
|
||||||
"id": 2,
|
|
||||||
"visa": visa,
|
"visa": visa,
|
||||||
|
"id": "jsonrpc",
|
||||||
|
"method": "EnumerateAccountStatistics",
|
||||||
"params": {
|
"params": {
|
||||||
"Query": {
|
"query": {
|
||||||
"PartnerId": partner_id,
|
"PartnerId": partner_id,
|
||||||
"DisplayColumns": COVE_COLUMNS,
|
|
||||||
"StartRecordNumber": start,
|
"StartRecordNumber": start,
|
||||||
"RecordCount": count,
|
"RecordsCount": count,
|
||||||
"Columns": COVE_COLUMNS,
|
"Columns": COVE_COLUMNS,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
resp = requests.post(url, json=payload, timeout=60)
|
resp = requests.post(
|
||||||
|
url,
|
||||||
|
json=payload,
|
||||||
|
headers={"Content-Type": "application/json"},
|
||||||
|
timeout=60,
|
||||||
|
)
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
data = resp.json()
|
data = resp.json()
|
||||||
except requests.RequestException as exc:
|
except requests.RequestException as exc:
|
||||||
@ -148,18 +158,32 @@ def _cove_enumerate(
|
|||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise CoveImportError(f"Cove EnumerateAccountStatistics response is not valid JSON: {exc}") from exc
|
raise CoveImportError(f"Cove EnumerateAccountStatistics response is not valid JSON: {exc}") from exc
|
||||||
|
|
||||||
result = data.get("result") or {}
|
if "error" in data and data["error"]:
|
||||||
if not result:
|
error = data["error"]
|
||||||
error = data.get("error") or {}
|
msg = error.get("message") or str(error) if isinstance(error, dict) else str(error)
|
||||||
raise CoveImportError(f"Cove EnumerateAccountStatistics failed: {error.get('message', 'unknown error')}")
|
raise CoveImportError(f"Cove EnumerateAccountStatistics failed: {msg}")
|
||||||
|
|
||||||
return result.get("result", []) or []
|
result = data.get("result")
|
||||||
|
if result is None:
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Unwrap possible nested result (same as test script)
|
||||||
|
if isinstance(result, dict) and "result" in result:
|
||||||
|
result = result["result"]
|
||||||
|
|
||||||
|
# Accounts can be a list directly or wrapped in an "Accounts" key
|
||||||
|
if isinstance(result, list):
|
||||||
|
return result
|
||||||
|
if isinstance(result, dict):
|
||||||
|
return result.get("Accounts", []) or []
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
def _flatten_settings(account: dict) -> dict:
|
def _flatten_settings(account: dict) -> dict:
|
||||||
"""Convert the Settings array in an account dict to a flat key→value dict.
|
"""Convert the Settings array in an account dict to a flat key→value dict.
|
||||||
|
|
||||||
Cove returns settings as a list of {Key, Value} objects.
|
Cove returns settings as a list of single-key dicts, e.g.:
|
||||||
|
[{"D09F00": "5"}, {"I1": "device name"}, ...]
|
||||||
This flattens them so we can do `flat['D09F00']` instead of iterating.
|
This flattens them so we can do `flat['D09F00']` instead of iterating.
|
||||||
"""
|
"""
|
||||||
flat: dict[str, Any] = {}
|
flat: dict[str, Any] = {}
|
||||||
@ -167,10 +191,7 @@ def _flatten_settings(account: dict) -> dict:
|
|||||||
if isinstance(settings_list, list):
|
if isinstance(settings_list, list):
|
||||||
for item in settings_list:
|
for item in settings_list:
|
||||||
if isinstance(item, dict):
|
if isinstance(item, dict):
|
||||||
key = item.get("Key") or item.get("key")
|
flat.update(item)
|
||||||
value = item.get("Value") if "Value" in item else item.get("value")
|
|
||||||
if key is not None:
|
|
||||||
flat[str(key)] = value
|
|
||||||
return flat
|
return flat
|
||||||
|
|
||||||
|
|
||||||
@ -274,8 +295,8 @@ def _process_account(account: dict, settings) -> bool:
|
|||||||
|
|
||||||
flat = _flatten_settings(account)
|
flat = _flatten_settings(account)
|
||||||
|
|
||||||
# AccountId – try both the top-level field and Settings
|
# AccountId is a top-level field (not in Settings)
|
||||||
account_id = account.get("AccountId") or account.get("AccountID") or flat.get("I18")
|
account_id = account.get("AccountId") or account.get("AccountID")
|
||||||
if not account_id:
|
if not account_id:
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user