Добавил таймауты для API-запросов, уточнил парсинг тегов и типизацию аргументов

This commit is contained in:
2026-07-06 16:01:24 +03:00
parent 8d19e1d43f
commit bd20ef4d48
5 changed files with 56 additions and 22 deletions

View File

@@ -1,6 +1,5 @@
from datetime import datetime
import logging
from typing import Union
from colorama import Fore
import requests
@@ -10,6 +9,7 @@ from config import ClockifyConfig, config, db
from classes import Activity, Tag, VetroProject
from exceptions import InvalidToken
from utils import parse_task_id, time_to_hour
from .constants import REQUEST_TIMEOUT
logger = logging.getLogger(__name__)
@@ -33,7 +33,12 @@ class ClockifyAPI(ClockifyConfig):
page-size: int - Размер страницы (по умолчанию 50)
"""
time_entries_url = self.time_entries_url
activities = requests.get(self.base_url + time_entries_url, params=kwargs, headers={"X-API-KEY": self.token}).json()
activities = requests.get(
self.base_url + time_entries_url,
params=kwargs,
headers={"X-API-KEY": self.token},
timeout=REQUEST_TIMEOUT,
).json()
if not activities:
return []
logger.info(f"Получено задач: {len(activities)}")
@@ -100,7 +105,7 @@ class ClockifyAPI(ClockifyConfig):
return result
def get_tags(self, is_updating=False, tag_id="", **kwargs) -> Union[list[Tag], Tag, None]:
def get_tags(self, is_updating=False, tag_id="", **kwargs) -> list[Tag] | Tag | None:
"""
Получает с сервера Clockify теги
name: str - optional
@@ -122,7 +127,8 @@ class ClockifyAPI(ClockifyConfig):
tags = requests.get(
self.base_url + self.tags_url,
params=params,
headers={"X-API-KEY": self.token}
headers={"X-API-KEY": self.token},
timeout=REQUEST_TIMEOUT,
).json()
for tag in tags:
@@ -150,10 +156,18 @@ class ClockifyAPI(ClockifyConfig):
"""
Получает с сервера Clockify Проекты (ДО) Ветро
"""
projects = requests.get(self.base_url + self.projects_url, headers={"X-API-KEY": self.token}).json()
projects = requests.get(
self.base_url + self.projects_url,
headers={"X-API-KEY": self.token},
timeout=REQUEST_TIMEOUT,
).json()
for project in projects:
if not project.get("archived"):
vetro_projects = requests.get(self.base_url + self.tasks_url(project.get("id")), headers={"X-API-KEY": self.token}).json()
vetro_projects = requests.get(
self.base_url + self.tasks_url(project.get("id")),
headers={"X-API-KEY": self.token},
timeout=REQUEST_TIMEOUT,
).json()
return [VetroProject(
id=vetro_project["id"],
title=vetro_project["name"]
@@ -180,7 +194,8 @@ class ClockifyAPI(ClockifyConfig):
"projectId": activity.project_id
}, headers={
"X-API-KEY": self.token
}
},
timeout=REQUEST_TIMEOUT,
)
if config.is_debug or resp.ok:
logger.info(f"{Fore.GREEN}{activity}{Fore.RESET}".ljust(60) + "")
@@ -193,7 +208,11 @@ class ClockifyAPI(ClockifyConfig):
def _check_token(self, token: str) -> str:
"""Проверяет токен"""
resp = requests.get(self.base_url + self.user_url, headers={"X-API-KEY": token})
resp = requests.get(
self.base_url + self.user_url,
headers={"X-API-KEY": token},
timeout=REQUEST_TIMEOUT,
)
if not resp.ok:
logger.error(resp.json())
raise InvalidToken
@@ -203,7 +222,11 @@ class ClockifyAPI(ClockifyConfig):
def get_user(self):
"""Получает нужные данные пользователя с сервера Clockify"""
resp = requests.get(self.base_url + self.user_url, headers={"X-API-KEY": self.token})
resp = requests.get(
self.base_url + self.user_url,
headers={"X-API-KEY": self.token},
timeout=REQUEST_TIMEOUT,
)
if not resp.ok:
logger.error(resp.json())
raise requests.exceptions.RequestException(Fore.RED + "Ошибка запроса." + Fore.RESET)

3
api/constants.py Normal file
View File

@@ -0,0 +1,3 @@
"""Константы API-клиентов."""
REQUEST_TIMEOUT = 30

View File

@@ -8,14 +8,11 @@ from config import RedmineConfig, config
from classes import Activity, ActivityType
from enums import ActivityTypes
from exceptions import InvalidToken
from .constants import REQUEST_TIMEOUT
logger = logging.getLogger(__name__)
WORKSPACE_ID = config.WORKSPACE_ID
USER_ID = config.USER_ID
class RedmineAPI(RedmineConfig):
"""API клиент для взаимодействия с Redmine.
@@ -52,7 +49,8 @@ class RedmineAPI(RedmineConfig):
"comments": activity.description,
"spent_on": activity.date_start.strftime(self.datetime_format)
}
}
},
timeout=REQUEST_TIMEOUT,
)
if config.is_debug or resp.ok:
activity.is_tracked = True
@@ -84,7 +82,11 @@ class RedmineAPI(RedmineConfig):
def get_time_entry_activities(self) -> list[ActivityTypes]:
"""Получает виды Деятельности с сервера Redmine"""
resp = requests.get(self.base_url + self.time_entry_activities, headers={"X-Redmine-API-KEY": self.token})
resp = requests.get(
self.base_url + self.time_entry_activities,
headers={"X-Redmine-API-KEY": self.token},
timeout=REQUEST_TIMEOUT,
)
logger.debug(resp.json())
if resp.ok:
return [
@@ -95,7 +97,11 @@ class RedmineAPI(RedmineConfig):
def get_user(self):
"""Проверяет токен и возвращает данные юзера"""
resp = requests.get(self.base_url + self.my_account_url, headers={"X-Redmine-Api-Key": self.token})
resp = requests.get(
self.base_url + self.my_account_url,
headers={"X-Redmine-Api-Key": self.token},
timeout=REQUEST_TIMEOUT,
)
if not resp.ok:
logger.error("Invalid Token for RedmineAPI")
raise InvalidToken
@@ -113,7 +119,7 @@ class RedmineAPI(RedmineConfig):
resp = requests.get(
self.base_url + self.issue.format(**{"id": issue_id}),
headers={"X-Redmine-Api-Key": self.token},
timeout=None
timeout=REQUEST_TIMEOUT,
)
if not resp.ok:
logger.error("Ошибка RedmineAPI")
@@ -138,7 +144,7 @@ class RedmineAPI(RedmineConfig):
self.base_url + self.issues,
headers={"X-Redmine-Api-Key": self.token},
params={"limit": 100, "offset": 100 * i, "status_id": "*"},
timeout=None
timeout=REQUEST_TIMEOUT,
)
if not resp.ok or not resp.json():
logger.error("Ошибка RedmineAPI")