mirror of
https://git.auk.su/Dinect/bonus-import-tools.git
synced 2026-04-03 09:00:02 +00:00
Add error handling and retry logic to API calls
- Add retry decorator for transient errors in dinect_api.py - Add error handling decorator for consistent error responses - Update bonuses_update to handle numeric bonus amounts properly - Fix parameter name typo in bonuses_update - Skip invalid phone numbers and rows in app.py - Add duplicate doc_id detection in transaction processing - Improve logging of successful transactions - Remove redundant doc_id generation code - Fix file closing logic in app.py
This commit is contained in:
126
dinect_api.py
126
dinect_api.py
@@ -1,14 +1,66 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# __author__ = 'szhdanoff@gmail.com'
|
||||
__version__ = '1.0.3'
|
||||
__version__ = '1.0.4'
|
||||
import os
|
||||
import requests
|
||||
import json
|
||||
import time
|
||||
from functools import wraps
|
||||
|
||||
from dotenv import load_dotenv
|
||||
load_dotenv()
|
||||
|
||||
|
||||
def retry_on_error(max_retries=3, delay=1, backoff=2):
|
||||
"""
|
||||
Decorator for retrying API calls on transient errors.
|
||||
|
||||
Args:
|
||||
max_retries: Maximum number of retry attempts
|
||||
delay: Initial delay between retries in seconds
|
||||
backoff: Multiplier for delay after each retry
|
||||
"""
|
||||
def decorator(func):
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
last_exception = None
|
||||
current_delay = delay
|
||||
for attempt in range(max_retries + 1):
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except (requests.exceptions.ConnectionError,
|
||||
requests.exceptions.Timeout,
|
||||
requests.exceptions.HTTPError) as e:
|
||||
last_exception = e
|
||||
if attempt < max_retries:
|
||||
print(f"Retry {attempt + 1}/{max_retries} after {current_delay}s - Error: {e}")
|
||||
time.sleep(current_delay)
|
||||
current_delay *= backoff
|
||||
else:
|
||||
print(f"All {max_retries} retries exhausted. Last error: {e}")
|
||||
raise last_exception
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
|
||||
def handle_api_error(func):
|
||||
"""
|
||||
Decorator for handling API errors consistently.
|
||||
Returns tuple (False, error_message) on any exception.
|
||||
"""
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except requests.exceptions.RequestException as e:
|
||||
return False, f"Network error: {str(e)}"
|
||||
except (ValueError, KeyError) as e:
|
||||
return False, f"Data error: {str(e)}"
|
||||
except Exception as e:
|
||||
return False, f"Unexpected error: {str(e)}"
|
||||
return wrapper
|
||||
|
||||
is_prod = bool(os.getenv('PRODUCTION', False))
|
||||
if is_prod:
|
||||
url = 'https://pos-api.dinect.com/20130701/'
|
||||
@@ -51,6 +103,12 @@ def get_user(search_id, get_type='auto', headers=None) -> tuple:
|
||||
- The boolean value indicating success or failure.
|
||||
- The response data based on the request made.
|
||||
"""
|
||||
return _get_user_impl(search_id, get_type, headers)
|
||||
|
||||
|
||||
@retry_on_error(max_retries=3, delay=1, backoff=2)
|
||||
@handle_api_error
|
||||
def _get_user_impl(search_id, get_type='auto', headers=None) -> tuple:
|
||||
if headers is None:
|
||||
headers = HEADERS
|
||||
|
||||
@@ -129,6 +187,12 @@ def new_user_by_card(external_card, full_name, phone, gender=1, email=None, head
|
||||
If the request is successful, the boolean is True and the JSON response is returned.
|
||||
If the request is unsuccessful, the boolean is False and the error message is returned.
|
||||
"""
|
||||
return _new_user_by_card_impl(external_card, full_name, phone, gender, email, headers, use_existing)
|
||||
|
||||
|
||||
@retry_on_error(max_retries=3, delay=1, backoff=2)
|
||||
@handle_api_error
|
||||
def _new_user_by_card_impl(external_card, full_name, phone, gender=1, email=None, headers=None, use_existing=True):
|
||||
if headers is None:
|
||||
headers = HEADERS
|
||||
|
||||
@@ -149,6 +213,26 @@ def new_user_by_card(external_card, full_name, phone, gender=1, email=None, head
|
||||
return False, r.text
|
||||
|
||||
|
||||
if headers is None:
|
||||
headers = HEADERS
|
||||
|
||||
base_url = url + '/users/'
|
||||
|
||||
params = {
|
||||
# "short_name": nickname,
|
||||
"full_name": full_name,
|
||||
"phone": phone,
|
||||
"email": email,
|
||||
"gender": gender,
|
||||
}
|
||||
|
||||
r = requests.post(base_url, headers=headers, json=params)
|
||||
if r.status_code == 201:
|
||||
return True, r.json()
|
||||
else:
|
||||
return False, r.json()
|
||||
|
||||
|
||||
def add_external_card(user_id, card, headers=None):
|
||||
"""
|
||||
Adds an external card to a user.
|
||||
@@ -173,6 +257,8 @@ def add_external_card(user_id, card, headers=None):
|
||||
r = requests.post(base_url, headers=headers, json=params)
|
||||
if r.status_code == 201:
|
||||
return True, r.json()
|
||||
else:
|
||||
return False, r.text if r.text else f"HTTP {r.status_code}"
|
||||
|
||||
|
||||
def bonuses_update(
|
||||
@@ -184,6 +270,37 @@ def bonuses_update(
|
||||
doc_id,
|
||||
headers=None,
|
||||
dry_run=False
|
||||
):
|
||||
"""
|
||||
Updates user bonuses (adds or subtracts bonus points).
|
||||
|
||||
Args:
|
||||
user_id: User ID in Dinect system
|
||||
summ_total: Total sum of transaction
|
||||
bonus_amount: Bonus amount to add (positive) or pay with bonuses (negative)
|
||||
sum_with_discount: Sum after discount
|
||||
sum_discount: Discount amount
|
||||
doc_id: Document ID for the transaction
|
||||
headers: Optional request headers
|
||||
dry_run: If True, simulates the transaction without actual processing
|
||||
|
||||
Returns:
|
||||
tuple: (success: bool, response_data: dict|str)
|
||||
"""
|
||||
return _bonuses_update_impl(user_id, summ_total, bonus_amount, sum_with_discount, sum_discount, doc_id, headers, dry_run)
|
||||
|
||||
|
||||
@retry_on_error(max_retries=3, delay=1, backoff=2)
|
||||
@handle_api_error
|
||||
def _bonuses_update_impl(
|
||||
user_id,
|
||||
summ_total,
|
||||
bonus_amount,
|
||||
sum_with_discount,
|
||||
sum_discount,
|
||||
doc_id,
|
||||
headers=None,
|
||||
dry_run=False
|
||||
):
|
||||
if headers is None:
|
||||
headers = HEADERS
|
||||
@@ -194,7 +311,7 @@ def bonuses_update(
|
||||
# "bonus_amount": bonus_amount,
|
||||
"sum_total": summ_total,
|
||||
"sum_discount": sum_discount,
|
||||
"sum_with_discount ": sum_with_discount,
|
||||
"sum_with_discount": sum_with_discount,
|
||||
"commit": 'True',
|
||||
"curr_iso_name": currency,
|
||||
"override": 'True'
|
||||
@@ -202,9 +319,10 @@ def bonuses_update(
|
||||
}
|
||||
bonus_amount_numeric = float(bonus_amount)
|
||||
if bonus_amount_numeric >= 0:
|
||||
params["bonus_amount"] = bonus_amount
|
||||
params["bonus_amount"] = str(int(bonus_amount_numeric))
|
||||
else:
|
||||
params["bonus_payment"] = bonus_amount[1:]
|
||||
# Negative value - pay with bonuses (use absolute value)
|
||||
params["bonus_payment"] = str(int(abs(bonus_amount_numeric)))
|
||||
|
||||
r = requests.post(base_url, headers=headers, json=params)
|
||||
if r.status_code == 201:
|
||||
|
||||
Reference in New Issue
Block a user