Files
ACORE/apps/portal_connector/client.py
T
lokses a35be94996 portal_connector: синхронизация PNMS/PVMS/MSorter
Клиенты и билдеры payload'ов, сервисы и Celery-задачи push identity/данных
во внешние порталы, HTTPS-guard в проде.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-09 18:03:55 +03:00

76 lines
2.4 KiB
Python

"""HTTP client ACORE → PNMS identity sync."""
from __future__ import annotations
import logging
from typing import Any
from urllib.parse import urlparse
import requests
from django.conf import settings
logger = logging.getLogger(__name__)
class PnmsClientError(RuntimeError):
pass
class PnmsClient:
def __init__(
self,
base_url: str | None = None,
token: str | None = None,
timeout: int = 60,
):
self.base_url = (base_url or getattr(settings, 'PNMS_BASE_URL', '') or '').rstrip('/')
self.token = token or getattr(settings, 'PNMS_ACORE_TOKEN', '') or ''
self.timeout = timeout
@property
def enabled(self) -> bool:
return bool(
getattr(settings, 'PNMS_SYNC_ENABLED', False)
and self.base_url
and self.token
)
def _headers(self) -> dict[str, str]:
return {
'X-Acore-Token': self.token,
'Content-Type': 'application/json',
'Accept': 'application/json',
}
def _ensure_tls(self) -> None:
"""В проде payload может содержать password_hash (Django PBKDF2)."""
if getattr(settings, 'DEBUG', True):
return
scheme = urlparse(self.base_url).scheme.lower()
if scheme != 'https':
raise PnmsClientError(
'PNMS_BASE_URL must use https:// when DEBUG is False '
'(identity payload may include password hashes).'
)
def post(self, path: str, payload: dict[str, Any]) -> dict[str, Any]:
if not self.enabled:
raise PnmsClientError('PNMS sync disabled or PNMS_BASE_URL/TOKEN missing')
self._ensure_tls()
url = f'{self.base_url}{path}'
resp = requests.post(
url, json=payload, headers=self._headers(), timeout=self.timeout
)
if resp.status_code >= 400:
raise PnmsClientError(
f'PNMS {path} HTTP {resp.status_code}: {resp.text[:500]}'
)
if not resp.content:
return {}
return resp.json()
def push_employees(self, employees: list[dict]) -> dict:
return self.post('/api/sync/from-acore/employees/', {'employees': employees})
def push_pharmacies(self, pharmacies: list[dict]) -> dict:
return self.post('/api/sync/from-acore/pharmacies/', {'pharmacies': pharmacies})