docker-ready
This commit is contained in:
42
sd_api.py
42
sd_api.py
@@ -6,6 +6,8 @@ import json
|
||||
import os
|
||||
from typing import List, Dict, Any
|
||||
from datetime import datetime
|
||||
from requests.adapters import HTTPAdapter
|
||||
from urllib3.util.retry import Retry
|
||||
|
||||
# Импортируем нашу конфигурацию
|
||||
import config
|
||||
@@ -19,6 +21,7 @@ class ServiceDeskAPIError(Exception):
|
||||
class ServiceDeskClient:
|
||||
"""
|
||||
Клиент для взаимодействия с REST API ServiceDesk.
|
||||
Оснащен механизмом повторных запросов для повышения отказоустойчивости.
|
||||
"""
|
||||
def __init__(self, access_key: str, base_url: str):
|
||||
if not access_key or not base_url:
|
||||
@@ -29,32 +32,55 @@ class ServiceDeskClient:
|
||||
# Устанавливаем accessKey как параметр по умолчанию для всех запросов
|
||||
self.session.params = {'accessKey': access_key}
|
||||
|
||||
# --- НАСТРОЙКА ЛОГИКИ ПОВТОРНЫХ ЗАПРОСОВ ---
|
||||
retry_strategy = Retry(
|
||||
total=3, # Общее количество повторных попыток
|
||||
backoff_factor=1, # Множитель для задержки (sleep_time = {backoff_factor} * (2 ** ({number of total retries} - 1)))
|
||||
# Задержки будут примерно 0.5с, 1с, 2с
|
||||
status_forcelist=[500, 502, 503, 504], # Коды, которые нужно повторять
|
||||
allowed_methods=["POST", "GET"] # Методы, для которых будет работать retry. API использует POST даже для поиска.
|
||||
)
|
||||
# Создаем адаптер с нашей стратегией
|
||||
adapter = HTTPAdapter(max_retries=retry_strategy)
|
||||
|
||||
# "Монтируем" адаптер для всех http и https запросов
|
||||
self.session.mount("https://", adapter)
|
||||
self.session.mount("http://", adapter)
|
||||
log.info("Клиент ServiceDesk инициализирован с логикой повторных запросов (retries).")
|
||||
|
||||
def _make_request(self, method: str, url: str, params: Dict = None, json_data: Dict = None) -> Any:
|
||||
"""
|
||||
Внутренний метод для выполнения HTTP-запросов.
|
||||
Теперь он автоматически использует логику retry, настроенную в __init__.
|
||||
|
||||
:param method: HTTP метод ('GET', 'POST', etc.)
|
||||
:param url: Полный URL для запроса.
|
||||
:param params: Дополнительные параметры URL (кроме accessKey).
|
||||
:param json_data: Тело запроса в формате JSON для POST/PUT.
|
||||
:return: Ответ сервера в виде JSON.
|
||||
:raises ServiceDeskAPIError: в случае ошибки API или сети.
|
||||
:raises ServiceDeskAPIError: в случае ошибки API или сети ПОСЛЕ всех попыток.
|
||||
"""
|
||||
try:
|
||||
# Теперь этот вызов будет автоматически повторяться в случае сбоев
|
||||
response = self.session.request(method, url, params=params, json=json_data, timeout=30)
|
||||
|
||||
# Проверяем на ошибки ПОСЛЕ успешного выполнения запроса (или исчерпания попыток)
|
||||
response.raise_for_status() # Вызовет исключение для кодов 4xx/5xx
|
||||
|
||||
# Некоторые ответы могут быть пустыми (например, при успешном редактировании)
|
||||
if response.status_code == 204 or not response.text:
|
||||
# Некоторые ответы могут быть пустыми (например, при успешном редактировании - 204)
|
||||
# или успешном создании (201)
|
||||
if response.status_code in [204, 201] or not response.text:
|
||||
return None
|
||||
|
||||
return response.json()
|
||||
|
||||
except requests.exceptions.HTTPError as e:
|
||||
# Эта ошибка возникнет, если после всех попыток сервер все равно вернул 4xx или 5xx
|
||||
error_message = f"HTTP Error: {e.response.status_code} for URL {url}. Response: {e.response.text}"
|
||||
log.error(error_message)
|
||||
raise ServiceDeskAPIError(error_message) from e
|
||||
except requests.exceptions.RequestException as e:
|
||||
# Эта ошибка возникнет, если после всех попыток не удалось подключиться к серверу (например, DNS или таймаут)
|
||||
error_message = f"Request failed for URL {url}: {e}"
|
||||
log.error(error_message)
|
||||
raise ServiceDeskAPIError(error_message) from e
|
||||
@@ -73,7 +99,8 @@ class ServiceDeskClient:
|
||||
}
|
||||
frs = self._make_request('POST', config.FIND_FRS_URL, params=params)
|
||||
log.info(f"Получено {len(frs)} записей о ФР.")
|
||||
return frs
|
||||
# frs может быть None если ответ пустой, вернем пустой список для консистентности
|
||||
return frs or []
|
||||
|
||||
def get_all_workstations(self) -> List[Dict]:
|
||||
"""Получает список всех рабочих станций."""
|
||||
@@ -83,7 +110,7 @@ class ServiceDeskClient:
|
||||
}
|
||||
workstations = self._make_request('POST', config.FIND_WORKSTATIONS_URL, params=params)
|
||||
log.info(f"Получено {len(workstations)} записей о рабочих станциях.")
|
||||
return workstations
|
||||
return workstations or []
|
||||
|
||||
def get_lookup_values(self, metaclass: str) -> List[Dict]:
|
||||
"""
|
||||
@@ -97,7 +124,7 @@ class ServiceDeskClient:
|
||||
params = {'attrs': 'UUID,title'}
|
||||
lookups = self._make_request('POST', url, params=params)
|
||||
log.info(f"Получено {len(lookups)} значений для {metaclass}.")
|
||||
return lookups
|
||||
return lookups or []
|
||||
|
||||
def update_fr(self, uuid: str, data: Dict) -> None:
|
||||
"""
|
||||
@@ -125,9 +152,6 @@ class ServiceDeskClient:
|
||||
return
|
||||
|
||||
log.info(f"Обновление объекта ФР с UUID: {uuid}...")
|
||||
# Примечание: старый код использовал POST с параметрами для редактирования.
|
||||
# Если API требует form-encoded data, а не JSON, нужно использовать `data=data` вместо `json=data`.
|
||||
# Судя по вашему коду, это POST-запрос с параметрами в URL, а не в теле.
|
||||
url = config.EDIT_FR_URL_TEMPLATE.format(uuid=uuid)
|
||||
self._make_request('POST', url, params=data)
|
||||
log.info(f"Объект {uuid} успешно обновлен.")
|
||||
|
||||
Reference in New Issue
Block a user