diff --git a/Examples/Ticker.py b/Examples/Ticker.py index 2649132..94282a6 100644 --- a/Examples/Ticker.py +++ b/Examples/Ticker.py @@ -1,10 +1,18 @@ import logging # Выводим лог на консоль и в файл -from datetime import datetime # Дата и время +from datetime import date, timedelta, datetime # Дата и время from math import log10 from TinvestPy import TinvestPy # Работа с T-Invest API из Python +def get_future_on_date(base, future_date=date.today()): # Фьючерсный контракт на дату + if future_date.day > 15 and future_date.month in (3, 6, 9, 12): # Если нужно переходить на следующий фьючерс + future_date += timedelta(days=30) # то добавляем месяц к дате + period = 'H' if future_date.month <= 3 else 'M' if future_date.month <= 6 else 'U' if future_date.month <= 9 else 'Z' # Месяц экспирации: 3-H, 6-M, 9-U, 12-Z + digit = future_date.year % 10 # Последняя цифра года + return f'SPBFUT.{base}{period}{digit}' + + if __name__ == '__main__': # Точка входа при запуске этого скрипта logger = logging.getLogger('TinvestPy.Ticker') # Будем вести лог tp_provider = TinvestPy() # Подключаемся к торговому счету @@ -15,7 +23,7 @@ handlers=[logging.FileHandler('Ticker.log', encoding='utf-8'), logging.StreamHandler()]) # Лог записываем в файл и выводим на консоль logging.Formatter.converter = lambda *args: datetime.now(tz=tp_provider.tz_msk).timetuple() # В логе время указываем по МСК - datanames = ('TQBR.SBER', 'TQBR.VTBR', 'SPBFUT.SiZ5', 'SPBFUT.RIZ5') # Кортеж тикеров + datanames = ('TQBR.SBER', 'TQBR.HYDR', get_future_on_date("Si"), get_future_on_date("RI"), 'SPBFUT.CNYRUBF', 'SPBFUT.IMOEXF') # Кортеж тикеров for dataname in datanames: # Пробегаемся по всем тикерам class_code, security_code = tp_provider.dataname_to_class_code_symbol(dataname) # Код режима торгов и тикер diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a69d4ca --- /dev/null +++ b/Makefile @@ -0,0 +1,817 @@ +.PHONY: help install venv run-accounts run-bars run-connect run-stream run-ticker \ + run-transactions clean update info + +# Цвета для вывода +GREEN := \033[0;32m +YELLOW := \033[1;33m +RED := \033[0;31m +CYAN := \033[0;36m +MAGENTA := \033[0;35m +NC := \033[0m + +# Переменные для парсинга данных из pyproject.toml +PROJECT_NAME := TinvestPy +PROJECT_VERSION := $(shell grep -E '^version\s*=\s*"' pyproject.toml | head -1 | sed -E 's/.*"([0-9.]+)".*/\1/') +PROJECT_DESCRIPTION := Библиотека-обертка, которая позволяет работать с T-Invest API брокера Т-Инвестиции из Python +PYTHON_REQUIRED := 3.12 +PROJECT_DEPS := $(shell grep -E '^dependencies\s*=\s*\[' pyproject.toml -A 20 | \ + grep -E '"[^"]+"' | \ + grep -v "github.com\|grpc/\|://\|https\?://" | \ + sed -E 's/.*"([^"]+)".*/\1/' | \ + sed -E 's/[>=<~!].*//' | \ + tr '\n' ' ') +PROJECT_AUTHOR_NAME := Чечет Игорь Александрович +PROJECT_URL_HOMEPAGE := https://github.com/cia76/TinvestPy +PROJECT_URL_REPOSITORY := https://github.com/cia76/TinvestPy + +# Переменные для виртуального окружения +VENV_DIR = .venv +PYTHON_CMD = python + +# Скрываем вывод пути директориии при рекурсивном вызове make +MAKEFLAGS += --no-print-directory + +# Путь к примерам (автоматическое определение) +EXAMPLES_PATH := $(shell if [ -d "Examples" ]; then echo "Examples"; \ + elif [ -d "TinvestPy/Examples" ]; then echo "TinvestPy/Examples"; \ + elif [ -d "${PROJECT_NAME}/Examples" ]; then echo "${PROJECT_NAME}/Examples"; \ + else echo "."; fi) + +# Цель - Показывает справку +help: + @printf "\n${CYAN}🐍 ДОСТУПНЫЕ КОМАНДЫ ДЛЯ ${PROJECT_NAME} v${PROJECT_VERSION}${NC}\n" + @printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n\n" + + @printf "${GREEN}📦ОСНОВНЫЕ КОМАНДЫ:${NC}\n" + @printf " ${YELLOW}make venv${NC} ${CYAN}⚙️ Создать виртуальное окружение\n" + @printf " ${YELLOW}make install${NC} ${CYAN}📦 Установить ${PROJECT_NAME} и зависимости${NC}\n" + @printf " ${YELLOW}make update${NC} ${CYAN}🔄 Обновить ${PROJECT_NAME} до актуальной версии\n\n" + + @printf "${GREEN}▶️ ЗАПУСК ПРИМЕРОВ (из папки ${EXAMPLES_PATH}):${NC}\n" + @printf " ${YELLOW}make run-connect${NC} ${CYAN}🔑 Connect.py (Ввод токена и проверка работы запросов/ответов и подписок)${NC}\n" + @printf " ${YELLOW}make run-ticker${NC} ${CYAN}📊 Ticker.py (Спецификация тикера)${NC}\n" + @printf " ${YELLOW}make run-bars${NC} ${CYAN}📈 Bars.py (История тикера)${NC}\n" + @printf " ${YELLOW}make run-accounts${NC} ${CYAN}💰 Accounts.py (Все торговые счета, позиции и заявки)${NC}\n" + @printf " ${YELLOW}make run-stream${NC} ${CYAN}📡 Stream.py (Подписка на котировки)${NC}\n" + @printf " ${YELLOW}make run-transactions${NC} ${CYAN}🔄 Transactions.py (Постановка и снятие заявок)${NC}\n\n" + + @printf "${GREEN}🛠️ ВСПОМОГАТЕЛЬНЫЕ КОМАНДЫ:${NC}\n" + @printf " ${YELLOW}make clean${NC} ${CYAN}🗑️ Очистка временных файлов${NC}\n" + @printf " ${YELLOW}make info${NC} ${CYAN}ℹ️ Подробная информация о проекте${NC}\n" + @printf " ${YELLOW}make help${NC} ${CYAN}❓ Показать это сообщение${NC}\n\n" + +# Цель - Создание виртуального окружения +venv: + @printf "\n${CYAN}🐍 СОЗДАНИЕ ВИРТУАЛЬНОГО ОКРУЖЕНИЯ ДЛЯ ${PROJECT_NAME}${NC}\n" + @printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n" + @printf "${YELLOW}🐍 Требуемая версия Python: ${PYTHON_REQUIRED}${NC}\n\n" + + @if [ ! -d "$(VENV_DIR)" ]; then \ + printf "${YELLOW}⚙️ Создаю виртуальное окружение с Python ${PYTHON_REQUIRED}...${NC}\n"; \ + $(PYTHON_CMD)$(PYTHON_REQUIRED) -m venv $(VENV_DIR); \ + \ + if [ $$? -eq 0 ]; then \ + printf "${GREEN}✅ Виртуальное окружение создано в $(VENV_DIR)${NC}\n"; \ + printf "${YELLOW}⚙️ Обновление pip...${NC}\n"; \ + $(VENV_DIR)/bin/python -m pip install --upgrade pip > /dev/null 2>&1; \ + printf "${GREEN}✅ pip обновлен${NC}\n"; \ + printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"; \ + else \ + printf "${RED}❌ Ошибка при создании виртуального окружения!${NC}\n"; \ + printf "${YELLOW} Убедитесь, что Python ${PYTHON_REQUIRED} установлен: $(PYTHON_CMD)$(PYTHON_REQUIRED) --version${NC}\n\n"; \ + exit 1; \ + fi \ + else \ + printf "${YELLOW}📁 Виртуальное окружение уже существует в $(VENV_DIR)${NC}\n"; \ + printf "${YELLOW}⚙️ Проверка pip...${NC}\n"; \ + $(VENV_DIR)/bin/python -m pip install --upgrade pip > /dev/null 2>&1; \ + printf "${GREEN}✅ pip готов к работе${NC}\n"; \ + printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"; \ + fi + + @printf "\n${CYAN}📋 СЛЕДУЮЩИЕ ШАГИ:${NC}\n" + @printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n\n" + + @printf "${GREEN}1. Активируйте виртуальное окружение:${NC}\n" + @printf " ${YELLOW}source $(VENV_DIR)/bin/activate${NC}\n\n" + + @printf "${GREEN}2. Установите ${PROJECT_NAME} и зависимости:${NC}\n" + @printf " ${YELLOW}make install${NC}\n\n" + + @printf "${GREEN}3. Запустите пример и следуйте инструкциям:${NC}\n" + @printf " ${YELLOW}make run-connect${NC}\n" + @printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n\n" + +# Цель - Установка пакета +install: + @printf "\n${CYAN}📦 УСТАНОВКА ${PROJECT_NAME} v${PROJECT_VERSION}${NC}\n" + @printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n" + @printf "${CYAN}📝 ${PROJECT_DESCRIPTION}${NC}\n" + @printf "${CYAN}👤 Автор: ${PROJECT_AUTHOR_NAME}${NC}\n" + @printf "${CYAN}🔗 Репозиторий: ${PROJECT_URL_REPOSITORY}${NC}\n\n" + + @printf "${YELLOW}🔧 ПРОВЕРКА ОКРУЖЕНИЯ${NC}\n" + @printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n" + + @if ! command -v uv >/dev/null 2>&1; then \ + printf "${YELLOW}⚠️ uv не найден. Устанавливаю...${NC}\n"; \ + pip install uv > /dev/null 2>&1; \ + if [ $$? -eq 0 ]; then \ + printf "${GREEN}✅ uv установлен${NC}\n\n"; \ + else \ + printf "${RED}❌ Не удалось установить uv${NC}\n"; \ + printf "${YELLOW} Установите uv вручную: pip install uv${NC}\n\n"; \ + exit 1; \ + fi \ + else \ + printf "${GREEN}✅ uv уже установлен${NC}\n\n"; \ + fi + + @printf "${YELLOW}🔍 ПРОВЕРКА UV.LOCK${NC}\n" + @printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n" + @if command -v uv >/dev/null 2>&1; then \ + if [ -f "uv.lock" ]; then \ + printf "${GREEN} ✓ uv.lock найден${NC}\n"; \ + if uv lock --check > /dev/null 2>&1; then \ + printf "${GREEN} ✓ uv.lock валиден и соответствует pyproject.toml${NC}\n"; \ + LOCK_VALID=true; \ + else \ + printf "${YELLOW} ⚠️ uv.lock требует обновления${NC}\n"; \ + LOCK_VALID=false; \ + fi; \ + else \ + printf "${YELLOW} ⚠️ uv.lock не найден, будет создан при установке${NC}\n"; \ + LOCK_VALID=false; \ + fi; \ + printf "\n"; \ + fi + + @VENV_CREATED=0; \ + USE_UV_RUN=0; \ + if [ ! -d "$(VENV_DIR)" ]; then \ + printf "${YELLOW}⚠️ Виртуальное окружение не найдено! ⚠️${NC}\n\n"; \ + printf "${CYAN}Для работы с проектом рекомендуется использовать виртуальное окружение.${NC}\n\n"; \ + printf "${GREEN}1. Создать виртуальное окружение (make venv)${NC}\n"; \ + printf "${GREEN}2. Продолжить установку без виртуального окружения (через uv run)${NC}\n\n"; \ + printf "${YELLOW}Выберите действие (1/2): ${NC}"; \ + read -r choice; \ + if [ "$$choice" = "1" ]; then \ + printf "\n${CYAN}Создаю виртуальное окружение через uv...${NC}\n\n"; \ + $(MAKE) venv; \ + VENV_CREATED=1; \ + printf "\n"; \ + if [ -z "$$VIRTUAL_ENV" ]; then \ + printf "${YELLOW}⚠️ ВНИМАНИЕ: Виртуальное окружение создано, но не активировано! ⚠️${NC}\n\n"; \ + printf "${CYAN}Для продолжения работы рекомендуется активировать окружение:${NC}\n\n"; \ + printf "${GREEN} source $(VENV_DIR)/bin/activate${NC}\n\n"; \ + printf "${YELLOW}Продолжить установку без активации (через uv run)? (y/n): ${NC}"; \ + read -r choice2; \ + if [ "$$choice2" != "y" ] && [ "$$choice2" != "Y" ]; then \ + printf "${RED}❌ Установка отменена. Активируйте окружение и повторите команду.${NC}\n\n"; \ + exit 1; \ + else \ + printf "${GREEN}✅ Продолжаем установку через uv run...${NC}\n\n"; \ + USE_UV_RUN=1; \ + fi; \ + else \ + printf "${GREEN}✅ Виртуальное окружение создано и активировано!${NC}\n\n"; \ + USE_UV_RUN=0; \ + fi; \ + elif [ "$$choice" = "2" ]; then \ + printf "${YELLOW}⚠️ Продолжаем установку без виртуального окружения (через uv run)...${NC}\n\n"; \ + USE_UV_RUN=1; \ + else \ + printf "${RED}❌ Неверный выбор. Установка отменена.${NC}\n\n"; \ + exit 1; \ + fi; \ + else \ + if [ -z "$$VIRTUAL_ENV" ]; then \ + printf "${YELLOW}⚠️ ВНИМАНИЕ: Виртуальное окружение существует, но не активировано! ⚠️${NC}\n\n"; \ + printf "${CYAN}Активируйте существующее окружение:${NC}\n\n"; \ + printf "${GREEN} source $(VENV_DIR)/bin/activate${NC}\n\n"; \ + printf "${YELLOW}Продолжить установку без активации (через uv run)? (y/n): ${NC}"; \ + read -r choice; \ + if [ "$$choice" != "y" ] && [ "$$choice" != "Y" ]; then \ + printf "${RED}❌ Установка отменена. Активируйте окружение и повторите команду.${NC}\n\n"; \ + exit 1; \ + else \ + printf "${GREEN}✅ Продолжаем установку через uv run...${NC}\n\n"; \ + USE_UV_RUN=1; \ + fi; \ + else \ + printf "${GREEN}✅ Виртуальное окружение активировано!${NC}\n\n"; \ + USE_UV_RUN=0; \ + fi; \ + fi + + @printf "${YELLOW}⚙️ УСТАНОВКА ПРОЕКТА${NC}\n" + @printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n" + + @if [ "$$USE_UV_RUN" = "1" ]; then \ + if uv run pip show $(PROJECT_NAME) > /dev/null 2>&1; then \ + CURRENT_VER=$$(uv run pip show $(PROJECT_NAME) | grep Version | awk '{print $$2}'); \ + printf "${GREEN}✅ ${PROJECT_NAME} уже установлен (версия $$CURRENT_VER)${NC}\n"; \ + else \ + printf "${YELLOW}⚠️ ${PROJECT_NAME} не установлен${NC}\n"; \ + printf "${YELLOW}⚙️ Установка ${PROJECT_NAME} v${PROJECT_VERSION} через uv...${NC}\n\n"; \ + if [ "$$LOCK_VALID" = "true" ]; then \ + printf "${GREEN} Использую существующий uv.lock для синхронизации...${NC}\n"; \ + uv run uv sync; \ + else \ + printf "${YELLOW} Обновляю uv.lock и синхронизирую...${NC}\n"; \ + uv run uv sync -q; \ + fi; \ + if [ $$? -eq 0 ]; then \ + printf "${GREEN}✅ ${PROJECT_NAME} успешно установлен!${NC}\n"; \ + else \ + printf "\n${RED}❌ Ошибка при установке ${PROJECT_NAME}${NC}\n"; \ + printf "${YELLOW}Попробуйте установить локальную версию:${NC}\n"; \ + printf " ${YELLOW}uv pip install .${NC}\n\n"; \ + exit 0; \ + fi \ + fi \ + else \ + if uv pip show $(PROJECT_NAME) > /dev/null 2>&1; then \ + CURRENT_VER=$$(uv pip show $(PROJECT_NAME) | grep Version | awk '{print $$2}'); \ + printf "${GREEN}✅ ${PROJECT_NAME} уже установлен (версия $$CURRENT_VER)${NC}\n"; \ + else \ + printf "${YELLOW}⚠️ ${PROJECT_NAME} не установлен${NC}\n"; \ + printf "${YELLOW}⚙️ Установка ${PROJECT_NAME} v${PROJECT_VERSION} через uv...${NC}\n\n"; \ + if [ "$$LOCK_VALID" = "true" ]; then \ + printf "${GREEN} Использую существующий uv.lock для синхронизации...${NC}\n"; \ + uv sync; \ + else \ + printf "${YELLOW} Обновляю uv.lock и синхронизирую...${NC}\n"; \ + uv sync -q; \ + fi; \ + if [ $$? -eq 0 ]; then \ + printf "${GREEN}✅ ${PROJECT_NAME} успешно установлен!${NC}\n"; \ + else \ + printf "\n${RED}❌ Ошибка при установке ${PROJECT_NAME}${NC}\n"; \ + printf "${YELLOW}Попробуйте установить локальную версию:${NC}\n"; \ + printf " ${YELLOW}uv pip install .${NC}\n\n"; \ + exit 0; \ + fi \ + fi \ + fi + + @printf "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n\n" + + @if [ "$$VENV_CREATED" = "1" ] && [ -z "$$VIRTUAL_ENV" ]; then \ + printf "${YELLOW}📢 ВАЖНОЕ НАПОМИНАНИЕ 📢${NC}\n"; \ + printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"; \ + printf "${GREEN}✅ Виртуальное окружение создано и библиотеки установлены!${NC}\n\n"; \ + printf "${YELLOW}Для дальнейшей работы с проектом АКТИВИРУЙТЕ окружение:${NC}\n\n"; \ + printf " ${GREEN}source $(VENV_DIR)/bin/activate${NC}\n\n"; \ + printf "${CYAN}После активации вы сможете запускать примеры командами:${NC}\n"; \ + printf " ${YELLOW}make run-connect${NC}\n"; \ + printf " ${YELLOW}make run-ticker${NC}\n"; \ + printf " ${YELLOW}make run-bars${NC}\n"; \ + printf " ${YELLOW}make run-accounts${NC}\n\n"; \ + printf "${CYAN}Или используйте uv run без активации:${NC}\n"; \ + printf " ${YELLOW}uv run python $(EXAMPLES_PATH)/Connect.py${NC}\n"; \ + printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n\n"; \ + fi + + @printf "${MAGENTA}🔑 ВАЖНАЯ ИНФОРМАЦИЯ 🔑${NC}\n" + @printf "${MAGENTA}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n" + @printf "${YELLOW}⚠️ Для первого запуска необходим токен T-Invest API ⚠️${NC}\n\n" + @printf "1. Получить токен в настройках профиля ${CYAN}\\033]8;;https://www.tinkoff.ru/invest/settings/api\\033\\\\Т-Инвестиций\\033]8;;\\033\\\\${NC}\n" + @printf "2. Создайте токен с правами ${GREEN}Чтение и Торговля${NC}\n" + @printf "3. Скопируйте токен в буфер обмена\n" + @printf "4. Выполните команду ${YELLOW}make run-connect${NC} и следуйте дальнейшим инструкциям\n\n" + + @printf "${GREEN}🚀 ${PROJECT_NAME} v${PROJECT_VERSION} ГОТОВ К ИСПОЛЬЗОВАНИЮ!${NC}\n" + @printf "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n\n" + +# Цели - Запуск примеров из папки Examples +run-connect: + @printf "\n${CYAN}▶️ ПОДГОТОВКА И ЗАПУСК Connect.py...${NC}\n" + @printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n\n" + + @printf "${YELLOW}🔑 ПРОВЕРКА ОКРУЖЕНИЯ${NC}\n" + @printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n" + @if [ -z "$$VIRTUAL_ENV" ]; then \ + printf "${RED}❌ ОШИБКА: Виртуальное окружение не активировано!${NC}\n\n"; \ + printf "${YELLOW}Активируйте окружение:${NC}\n"; \ + printf " ${GREEN}source $(VENV_DIR)/bin/activate${NC}\n\n"; \ + exit 1; \ + else \ + printf "${GREEN}✅ Виртуальное окружение активировано!${NC}\n\n"; \ + fi + + @printf "${YELLOW}🔑 ПРОВЕРКА ТОКЕНА${NC}\n" + @printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n" + @if [ -n "$(TINVEST_TOKEN)" ]; then \ + printf "${GREEN}✓ Токен получен из аргумента командной строки${NC}\n\n"; \ + TOKEN="$(TINVEST_TOKEN)"; \ + TOKEN_LENGTH=$$(printf "%s" "$$TOKEN" | wc -c | tr -d ' '); \ + printf "📌 Длина: ${YELLOW}%s символов${NC}\n" "$$TOKEN_LENGTH"; \ + printf "📌 Начинается с: ${YELLOW}%s...${NC}\n" "$$(printf "%s" "$$TOKEN" | cut -c1-20)"; \ + printf "📌 Заканчивается на: ${YELLOW}...%s${NC}\n\n" "$$(printf "%s" "$$TOKEN" | rev | cut -c1-20 | rev)"; \ + printf "${YELLOW}💾 Сохраняем токен в keyring...${NC}\n\n"; \ + if python3 -c "import keyring; keyring.set_password('TinvestPy', 'token', '$$TOKEN')" 2>/dev/null; then \ + printf "${GREEN}✅ Токен успешно сохранен в keyring!${NC}\n\n"; \ + else \ + printf "${RED}❌ Ошибка при сохранении токена в keyring${NC}\n"; \ + printf "${YELLOW} Убедитесь, что keyring настроен корректно${NC}\n\n"; \ + fi; \ + else \ + printf "${YELLOW}🔍 Проверка токена в keyring...${NC}\n\n"; \ + SAVED_TOKEN=$$(python3 -c "import keyring; print(keyring.get_password('TinvestPy', 'token') or '')" 2>/dev/null); \ + if [ -n "$$SAVED_TOKEN" ]; then \ + printf "${GREEN}✓ Найден сохраненный токен в keyring${NC}\n\n"; \ + TOKEN_LENGTH=$$(printf "%s" "$$SAVED_TOKEN" | wc -c | tr -d ' '); \ + printf "📌 Длина: ${YELLOW}%s символов${NC}\n" "$$TOKEN_LENGTH"; \ + printf "📌 Начинается с: ${YELLOW}%s...${NC}\n" "$$(printf "%s" "$$SAVED_TOKEN" | cut -c1-20)"; \ + printf "📌 Заканчивается на: ${YELLOW}...%s${NC}\n\n" "$$(printf "%s" "$$SAVED_TOKEN" | rev | cut -c1-20 | rev)"; \ + printf "${YELLOW}⚠️ Использовать этот токен? (y/n): ${NC}"; \ + read -r USE_SAVED; \ + if [ "$$USE_SAVED" = "y" ] || [ "$$USE_SAVED" = "Y" ]; then \ + printf "\n${GREEN}✓ Будет использован сохраненный токен${NC}\n\n"; \ + TOKEN="$$SAVED_TOKEN"; \ + else \ + printf "\n${YELLOW}✗ Будет введен новый токен${NC}\n\n"; \ + GET_NEW_TOKEN="yes"; \ + fi; \ + else \ + printf "${YELLOW}✗ Токен не найден в keyring${NC}\n\n"; \ + GET_NEW_TOKEN="yes"; \ + fi; \ + fi; \ + \ + if [ "$$GET_NEW_TOKEN" = "yes" ] && [ -z "$$TOKEN" ]; then \ + printf "${YELLOW}❓ ВЫБОР ТИПА ТОКЕНА${NC}\n"; \ + printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"; \ + printf "${YELLOW}Сколько символов у вашего токена?${NC}\n"; \ + printf " ${GREEN}1)${NC} Более 1 000 символов (примерно)\n"; \ + printf " ${GREEN}2)${NC} Менее 1 000 символов (примерно)\n\n"; \ + printf "${YELLOW}Выберите вариант (1/2): ${NC}"; \ + read -r TOKEN; \ + printf "\n"; \ + \ + if [ "$$TOKEN" = "1" ]; then \ + printf "${YELLOW}⚡️ ЗАПУСКАЮ С ТОКЕНОМ...${NC}\n"; \ + printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"; \ + printf "${YELLOW}Запустите команду с токеном:${NC}\n"; \ + printf " ${GREEN}make run-connect TINVEST_TOKEN='ваш_токен'${NC}\n\n"; \ + exit 0; \ + elif [ "$$TOKEN" = "2" ]; then \ + printf "${YELLOW}📝 ВВОД ТОКЕНА${NC}\n"; \ + printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"; \ + printf "${YELLOW}Вставьте токен и нажмите Enter (символы не отображаются для безопасности):${NC}\n"; \ + printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"; \ + stty -echo; read -r TOKEN; stty echo; \ + printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n\n"; \ + \ + if [ -z "$$TOKEN" ]; then \ + printf "${RED}❌ Токен не может быть пустым${NC}\n\n"; \ + exit 1; \ + fi; \ + \ + printf "${YELLOW}💾 СОХРАНЕНИЕ В KEYRING${NC}\n"; \ + printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"; \ + if python3 -c "import keyring; keyring.set_password('TinvestPy', 'token', '$$TOKEN')" 2>/dev/null; then \ + TOKEN_LENGTH=$$(printf "%s" "$$TOKEN" | wc -c | tr -d ' '); \ + printf "${GREEN}✅ Токен успешно сохранен в keyring!${NC}\n\n"; \ + printf "📌 Длина: ${YELLOW}%s символов${NC}\n" "$$TOKEN_LENGTH"; \ + printf "📌 Начинается с: ${YELLOW}%s...${NC}\n" "$$(printf "%s" "$$TOKEN" | cut -c1-20)"; \ + printf "📌 Заканчивается на: ${YELLOW}...%s${NC}\n\n" "$$(printf "%s" "$$TOKEN" | rev | cut -c1-20 | rev)"; \ + else \ + printf "${RED}❌ Ошибка при сохранении токена в keyring${NC}\n"; \ + printf "${YELLOW} Убедитесь, что keyring настроен корректно${NC}\n\n"; \ + exit 1; \ + fi; \ + else \ + printf "${RED}❌ Неверный выбор${NC}\n\n"; \ + exit 1; \ + fi; \ + fi; \ + \ + printf "${YELLOW}🚀 ЗАПУСКАЮ Connect.py...${NC}\n"; \ + printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n\n"; \ + cd $(EXAMPLES_PATH) && python3 Connect.py; \ + EXIT_CODE=$$?; \ + printf "\n${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"; \ + if [ $$EXIT_CODE -eq 0 ]; then \ + printf "${GREEN}✅ Запуск Connect.py выполнен успешно!${NC}\n\n"; \ + printf "${CYAN}📚 ДРУГИЕ КОМАНДЫ ДЛЯ ЗАПУСКА ПРИМЕРОВ${NC}\n"; \ + printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n\n"; \ + printf " ${YELLOW}make run-ticker${NC} - Ticker.py (Спецификация тикера)\n"; \ + printf " ${YELLOW}make run-bars${NC} - Bars.py (История тикера)\n"; \ + printf " ${YELLOW}make run-accounts${NC} - Accounts.py (Все торговые счета, позиции и заявки)\n"; \ + printf " ${YELLOW}make run-stream${NC} - Stream.py (Подписка на котировки)\n"; \ + printf " ${YELLOW}make run-transactions${NC} - Transactions.py (Постановка и снятие заявок)\n\n"; \ + else \ + printf "${RED}❌ Ошибка при выполнении Connect.py${NC}\n\n"; \ + printf "${YELLOW}📝 ДЕЙСТВИЯ ПРИ ОШИБКЕ${NC}\n"; \ + printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"; \ + printf "${YELLOW}Попробуйте сохранить новый токен в keyring:${NC}\n\n"; \ + printf " ${GREEN}make run-connect TINVEST_TOKEN='ваш_токен'${NC}\n\n"; \ + exit 0; \ + fi + +run-ticker: + @printf "\n${CYAN}▶️ ЗАПУСКАЮ Ticker.py... (${PROJECT_NAME} v${PROJECT_VERSION})${NC}\n" + @printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n\n" + @printf "${CYAN}📋 Описание:${NC} Спецификация тикера.\n\n" + @printf "${YELLOW}⚠️ Убедитесь, что токен установлен при первом запуске ⚠️${NC}\n\n" + @printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n" + @printf "${CYAN}Продолжить запуск? (y/n): ${NC}"; \ + read choice; \ + if [ "$$choice" = "y" ] || [ "$$choice" = "Y" ]; then \ + printf "\n${GREEN}🚀 Запускаю Ticker.py...${NC}\n"; \ + printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n\n"; \ + cd ${EXAMPLES_PATH} && python Ticker.py; \ + printf "\n${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"; \ + printf "${GREEN}✅ Запуск Ticker.py выполнен успешно!${NC}\n\n"; \ + printf "${CYAN}📚 ДРУГИЕ КОМАНДЫ ДЛЯ ЗАПУСКА ПРИМЕРОВ${NC}\n"; \ + printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n\n"; \ + printf " ${YELLOW}make run-bars${NC} - Bars.py (История тикера)\n"; \ + printf " ${YELLOW}make run-accounts${NC} - Accounts.py (Все торговые счета, позиции и заявки)\n"; \ + printf " ${YELLOW}make run-stream${NC} - Stream.py (Подписка на котировки)\n"; \ + printf " ${YELLOW}make run-transactions${NC} - Transactions.py (Постановка и снятие заявок)\n"; \ + printf " ${YELLOW}make run-connect${NC} - Connect.py (Ввод токена и проверка работы запросов/ответов и подписок)\n\n"; \ + else \ + printf "${YELLOW}⏭️ Запуск отменен${NC}\n\n"; \ + fi + +run-bars: + @printf "\n${CYAN}▶️ ЗАПУСКАЮ Bars.py... (${PROJECT_NAME} v${PROJECT_VERSION})${NC}\n" + @printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n\n" + @printf "${CYAN}📋 Описание:${NC} История тикера.\n\n" + @printf "${YELLOW}⚠️ Убедитесь, что токен установлен при первом запуске ⚠️${NC}\n\n" + @printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n" + @printf "${CYAN}Продолжить запуск? (y/n): ${NC}"; \ + read choice; \ + if [ "$$choice" = "y" ] || [ "$$choice" = "Y" ]; then \ + printf "\n${GREEN}🔧 ПОДГОТОВКА К ЗАПУСКУ${NC}\n"; \ + printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n\n"; \ + printf "${YELLOW}🔍 Проверка pandas...${NC}\n"; \ + if python -c "import pandas" 2>/dev/null; then \ + printf "${GREEN}✓ pandas уже установлен${NC}\n"; \ + PANDAS_INSTALLED=0; \ + else \ + printf "${YELLOW}📦 Устанавливаю pandas через uv...${NC}\n"; \ + if uv pip install pandas --quiet > /dev/null 2>&1; then \ + printf "${GREEN}✓ pandas установлен${NC}\n"; \ + PANDAS_INSTALLED=1; \ + else \ + printf "${RED}❌ Ошибка установки pandas${NC}\n"; \ + PANDAS_INSTALLED=0; \ + fi; \ + fi; \ + printf "\n"; \ + printf "${YELLOW}📁 Создание папок для данных...${NC}\n"; \ + mkdir -p ${EXAMPLES_PATH}/../../Data/Tinkoff; \ + if [ -d "${EXAMPLES_PATH}/../../Data/Tinkoff" ]; then \ + printf "${GREEN}✓ Папка Data/Tinkoff создана${NC}\n\n"; \ + else \ + printf "${YELLOW}⚠️ Не удалось создать папку Data/Tinkoff${NC}\n\n"; \ + fi; \ + printf "${GREEN}🚀 ЗАПУСКАЮ Bars.py...${NC}\n"; \ + printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n\n"; \ + cd ${EXAMPLES_PATH} && python Bars.py; \ + SCRIPT_EXIT_CODE=$$?; \ + printf "\n${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"; \ + printf "${GREEN}✅ Запуск Bars.py выполнен успешно!${NC}\n\n"; \ + if [ "$$PANDAS_INSTALLED" = "1" ]; then \ + printf "${YELLOW}🗑️ Удаляю pandas через uv...${NC}\n"; \ + yes | uv pip uninstall pandas -q > /dev/null 2>&1 && \ + printf "${GREEN}✓ pandas удален${NC}\n" || \ + printf "${YELLOW}⚠️ Не удалось удалить pandas${NC}\n"; \ + printf "\n"; \ + fi; \ + printf "${CYAN}📚 ДРУГИЕ КОМАНДЫ ДЛЯ ЗАПУСКА ПРИМЕРОВ${NC}\n"; \ + printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n\n"; \ + printf " ${YELLOW}make run-accounts${NC} - Accounts.py (Все торговые счета, позиции и заявки)\n"; \ + printf " ${YELLOW}make run-stream${NC} - Stream.py (Подписка на котировки)\n"; \ + printf " ${YELLOW}make run-transactions${NC} - Transactions.py (Постановка и снятие заявок)\n"; \ + printf " ${YELLOW}make run-connect${NC} - Connect.py (Ввод токена и проверка работы запросов/ответов и подписок)\n"; \ + printf " ${YELLOW}make run-ticker${NC} - Ticker.py (Спецификация тикера)\n\n"; \ + exit $$SCRIPT_EXIT_CODE; \ + else \ + printf "${YELLOW}⏭️ Запуск отменен${NC}\n\n"; \ + fi + +run-accounts: + @printf "\n${CYAN}▶️ ЗАПУСКАЮ Accounts.py... (${PROJECT_NAME} v${PROJECT_VERSION})${NC}\n" + @printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n\n" + @printf "${CYAN}📋 Описание:${NC} Все торговые счета, позиции и заявки.\n\n" + @printf "${YELLOW}⚠️ Убедитесь, что токен установлен при первом запуске ⚠️${NC}\n\n" + @printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n" + @printf "${CYAN}Продолжить запуск? (y/n): ${NC}"; \ + read choice; \ + if [ "$$choice" = "y" ] || [ "$$choice" = "Y" ]; then \ + printf "\n${GREEN}🚀 ЗАПУСКАЮ Accounts.py...${NC}\n"; \ + printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n\n"; \ + cd ${EXAMPLES_PATH} && python Accounts.py; \ + printf "\n${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"; \ + printf "${GREEN}✅ Запуск Accounts.py выполнен успешно!${NC}\n\n"; \ + printf "${CYAN}📚 ДРУГИЕ КОМАНДЫ ДЛЯ ЗАПУСКА ПРИМЕРОВ${NC}\n"; \ + printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n\n"; \ + printf " ${YELLOW}make run-stream${NC} - Stream.py (Подписка на котировки)\n"; \ + printf " ${YELLOW}make run-transactions${NC} - Transactions.py (Постановка и снятие заявок)\n"; \ + printf " ${YELLOW}make run-connect${NC} - Connect.py (Ввод токена и проверка работы запросов/ответов и подписок)\n"; \ + printf " ${YELLOW}make run-ticker${NC} - Ticker.py (Спецификация тикера)\n"; \ + printf " ${YELLOW}make run-bars${NC} - Bars.py (История тикера)\n\n"; \ + else \ + printf "${YELLOW}⏭️ Запуск отменен${NC}\n\n"; \ + fi + +run-stream: + @printf "\n${CYAN}▶️ ЗАПУСКАЮ Stream.py... (${PROJECT_NAME} v${PROJECT_VERSION})${NC}\n" + @printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n\n" + @printf "${CYAN}📋 Описание:${NC} Подписка на котировки.\n\n" + @printf "${YELLOW}⚠️ Убедитесь, что токен установлен при первом запуске ⚠️${NC}\n\n" + @printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n" + @printf "${CYAN}Продолжить запуск? (y/n): ${NC}"; \ + read choice; \ + if [ "$$choice" = "y" ] || [ "$$choice" = "Y" ]; then \ + printf "\n${GREEN}🚀 ЗАПУСКАЮ Stream.py...${NC}\n"; \ + printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n\n"; \ + cd ${EXAMPLES_PATH} && python Stream.py; \ + printf "\n${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"; \ + printf "${GREEN}✅ Запуск Stream.py выполнен успешно!${NC}\n\n"; \ + printf "${CYAN}📚 ДРУГИЕ КОМАНДЫ ДЛЯ ЗАПУСКА ПРИМЕРОВ${NC}\n"; \ + printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n\n"; \ + printf " ${YELLOW}make run-transactions${NC} - Transactions.py (Постановка и снятие заявок)\n"; \ + printf " ${YELLOW}make run-connect${NC} - Connect.py (Ввод токена и проверка работы запросов/ответов и подписок)\n"; \ + printf " ${YELLOW}make run-ticker${NC} - Ticker.py (Спецификация тикера)\n"; \ + printf " ${YELLOW}make run-bars${NC} - Bars.py (История тикера)\n"; \ + printf " ${YELLOW}make run-accounts${NC} - Accounts.py (Все торговые счета, позиции и заявки)\n\n"; \ + else \ + printf "${YELLOW}⏭️ Запуск отменен${NC}\n\n"; \ + fi + +run-transactions: + @printf "\n${CYAN}▶️ ЗАПУСКАЮ Transactions.py... (${PROJECT_NAME} v${PROJECT_VERSION})${NC}\n" + @printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n\n" + @printf "${CYAN}📋 Описание:${NC} Постановка и снятие заявок.\n\n" + @printf "${YELLOW}⚠️ Убедитесь, что токен установлен при первом запуске ⚠️${NC}\n\n" + @printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n" + @printf "${CYAN}Продолжить запуск? (y/n): ${NC}"; \ + read choice; \ + if [ "$$choice" = "y" ] || [ "$$choice" = "Y" ]; then \ + printf "\n${GREEN}🚀 ЗАПУСКАЮ Transactions.py...${NC}\n"; \ + printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n\n"; \ + cd ${EXAMPLES_PATH} && python Transactions.py; \ + printf "\n${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"; \ + printf "${GREEN}✅ Запуск Transactions.py выполнен успешно!${NC}\n\n"; \ + printf "${CYAN}📚 ДРУГИЕ КОМАНДЫ ДЛЯ ЗАПУСКА ПРИМЕРОВ${NC}\n"; \ + printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n\n"; \ + printf " ${YELLOW}make run-connect${NC} - Connect.py (Ввод токена и проверка работы запросов/ответов и подписок)\n"; \ + printf " ${YELLOW}make run-ticker${NC} - Ticker.py (Спецификация тикера)\n"; \ + printf " ${YELLOW}make run-bars${NC} - Bars.py (История тикера)\n"; \ + printf " ${YELLOW}make run-accounts${NC} - Accounts.py (Все торговые счета, позиции и заявки)\n"; \ + printf " ${YELLOW}make run-stream${NC} - Stream.py (Подписка на котировки)\n\n"; \ + else \ + printf "${YELLOW}⏭️ Запуск отменен${NC}\n\n"; \ + fi + +# Цель - Обновление пакета из GitHub +update: + @printf "\n${CYAN}🔄 ОБНОВЛЕНИЕ ${PROJECT_NAME} ИЗ GITHUB${NC}\n" + @printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n\n" + + @if ! command -v uv >/dev/null 2>&1; then \ + printf "${YELLOW}⚠️ uv не найден. Устанавливаю...${NC}\n"; \ + pip install uv > /dev/null 2>&1; \ + if [ $$? -eq 0 ]; then \ + printf "${GREEN}✅ uv установлен${NC}\n\n"; \ + else \ + printf "${RED}❌ Не удалось установить uv${NC}\n"; \ + printf "${YELLOW} Установите uv вручную: pip install uv${NC}\n\n"; \ + exit 1; \ + fi \ + else \ + printf "${GREEN}✅ uv уже установлен${NC}\n\n"; \ + fi + + @printf "${YELLOW}🔍 ПРОВЕРКА ТЕКУЩЕЙ ВЕРСИИ${NC}\n" + @printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n" + + @if [ ! -d "$(VENV_DIR)" ]; then \ + printf "${YELLOW}⚠️ Виртуальное окружение не найдено!${NC}\n\n"; \ + printf "${CYAN}Создаю виртуальное окружение...${NC}\n\n"; \ + $(MAKE) venv; \ + printf "\n"; \ + fi + + @if uv pip show $(PROJECT_NAME) > /dev/null 2>&1; then \ + CURRENT_VER=$$(uv pip show $(PROJECT_NAME) | grep Version | awk '{print $$2}'); \ + AVAILABLE_VER_NORM=$$(echo "$(PROJECT_VERSION)" | sed -E 's/\.0+([1-9])/.\1/g; s/\.0+$$//'); \ + printf "${GREEN}✓ Текущая версия: ${YELLOW}%s${NC}\n" "$$CURRENT_VER"; \ + printf "${GREEN}✓ Доступная версия: ${YELLOW}%s${NC}\n\n" "$$AVAILABLE_VER_NORM"; \ + \ + CURRENT_VER_NORM=$$(echo "$$CURRENT_VER" | sed -E 's/\.0+([1-9])/.\1/g; s/\.0+$$//'); \ + \ + if [ "$$CURRENT_VER_NORM" = "$$AVAILABLE_VER_NORM" ]; then \ + printf "${GREEN}✅ У вас уже установлена актуальная версия ${PROJECT_NAME} v$$CURRENT_VER${NC}\n\n"; \ + printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"; \ + exit 0; \ + else \ + printf "${YELLOW}⚠️ Доступна новая версия: ${GREEN}%s${NC} → ${CYAN}%s${NC}\n\n" "$$CURRENT_VER" "$$AVAILABLE_VER_NORM"; \ + UPDATE_NEEDED="yes"; \ + fi; \ + else \ + printf "${YELLOW}⚠️ ${PROJECT_NAME} не найден в текущем окружении${NC}\n\n"; \ + printf "${YELLOW}⚙️ Выполните установку:${NC}\n"; \ + printf " ${GREEN}make install${NC}\n\n"; \ + exit 0; \ + fi + + @if [ "$$UPDATE_NEEDED" = "yes" ]; then \ + printf "${YELLOW}🔧 ПРОВЕРКА ОКРУЖЕНИЯ${NC}\n"; \ + printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"; \ + if [ -z "$$VIRTUAL_ENV" ]; then \ + printf "${YELLOW}⚠️ Виртуальное окружение не активировано!${NC}\n"; \ + printf "${CYAN}uv автоматически использует $(VENV_DIR)${NC}\n\n"; \ + else \ + printf "${GREEN}✅ Виртуальное окружение активировано!${NC}\n\n"; \ + fi; \ + \ + printf "${YELLOW}❓ ПОДТВЕРЖДЕНИЕ ОБНОВЛЕНИЯ${NC}\n"; \ + printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"; \ + printf "${YELLOW}Обновить ${PROJECT_NAME} с версии ${RED}%s${NC} → ${GREEN}%s${NC}? (y/n): " "$$CURRENT_VER" "$$AVAILABLE_VER_NORM"; \ + read choice; \ + if [ "$$choice" != "y" ] && [ "$$choice" != "Y" ]; then \ + printf "${YELLOW}⏭️ Обновление отменено.${NC}\n\n"; \ + exit 0; \ + fi; \ + printf "\n"; \ + \ + printf "${YELLOW}⚙️ ПРОЦЕСС ОБНОВЛЕНИЯ${NC}\n"; \ + printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"; \ + printf "${YELLOW}Обновление ${PROJECT_NAME} с $$CURRENT_VER до $$AVAILABLE_VER_NORM...${NC}\n\n"; \ + if uv pip install -q --upgrade --force-reinstall git+$(PROJECT_URL_REPOSITORY).git; then \ + NEW_VER=$$(uv pip show $(PROJECT_NAME) | grep Version | awk '{print $$2}'); \ + printf "${GREEN}✅ ${PROJECT_NAME} успешно обновлен!${NC}\n\n"; \ + printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"; \ + printf "${GREEN}📦 ${PROJECT_NAME}: ${RED}%s${NC} → ${GREEN}%s${NC}\n" "$$CURRENT_VER" "$$NEW_VER"; \ + printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n\n"; \ + else \ + printf "\n${RED}❌ Ошибка при обновлении ${PROJECT_NAME}${NC}\n"; \ + printf "${YELLOW} Попробуйте выполнить:${NC}\n"; \ + printf " ${GREEN}uv pip install --upgrade --force-reinstall git+$(PROJECT_URL_REPOSITORY).git${NC}\n\n"; \ + exit 1; \ + fi; \ + \ + printf "${CYAN}📦 ИНФОРМАЦИЯ О ПРОЕКТЕ${NC}\n"; \ + printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"; \ + printf "${CYAN}📦 ${PROJECT_NAME} v$(PROJECT_VERSION)${NC}\n"; \ + printf "${CYAN}📝 ${PROJECT_DESCRIPTION}${NC}\n"; \ + printf "${CYAN}👤 Автор: ${PROJECT_AUTHOR_NAME}${NC}\n"; \ + printf "${CYAN}🔗 Репозиторий: ${PROJECT_URL_REPOSITORY}${NC}\n\n"; \ + printf "${GREEN}✅ Обновление завершено успешно!${NC}\n\n"; \ + fi + +# Цель - Очистка временных файлов, файлов с логами и виртуального окружения +clean: + @printf "\n${CYAN}🗑️ ОЧИСТКА ВРЕМЕННЫХ ФАЙЛОВ ${PROJECT_NAME}${NC}\n" + @printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n" + @if [ -n "$$VIRTUAL_ENV" ]; then \ + printf "\n${RED}⚠️ ВНИМАНИЕ: Виртуальное окружение активно!${NC}\n\n"; \ + printf "${YELLOW}Путь: $$VIRTUAL_ENV${NC}\n\n"; \ + printf "${CYAN}Перед очисткой рекомендуется деактивировать окружение командой:${NC}\n\n"; \ + printf " ${GREEN}deactivate${NC}\n\n"; \ + printf "${YELLOW}Продолжить очистку при активном окружении? (y/n): ${NC}"; \ + read choice; \ + if [ "$$choice" != "y" ] && [ "$$choice" != "Y" ]; then \ + printf "${RED}✗ Очистка отменена${NC}\n\n"; \ + exit 0; \ + else \ + printf "\n"; \ + CONTINUE_CLEAN=1; \ + fi \ + else \ + CONTINUE_CLEAN=1; \ + fi; \ + \ + if [ "$$CONTINUE_CLEAN" = "1" ]; then \ + ACTIVE_VENV="$${VIRTUAL_ENV:-}"; \ + \ + printf "\n${YELLOW}🔍 ПОИСК LOG ФАЙЛОВ${NC}\n"; \ + printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"; \ + LOG_FILES=$$(find ${EXAMPLES_PATH} -name "*.log" -type f 2>/dev/null | wc -l); \ + if [ "$$LOG_FILES" -gt 0 ]; then \ + printf "${YELLOW}⚠️ Найдено log файлов: $$LOG_FILES${NC}\n"; \ + printf "${RED}Удалить все log файлы? (y/n): ${NC}"; \ + read choice; \ + if [ "$$choice" = "y" ] || [ "$$choice" = "Y" ]; then \ + find ${EXAMPLES_PATH} -name "*.log" -type f -delete 2>/dev/null; \ + printf "${GREEN}✓ Log файлы удалены${NC}\n\n"; \ + else \ + printf "${YELLOW}✗ Log файлы сохранены${NC}\n\n"; \ + fi; \ + else \ + printf "${GREEN}✓ Log файлы не найдены${NC}\n\n"; \ + fi; \ + \ + printf "${YELLOW}📁 ВИРТУАЛЬНОЕ ОКРУЖЕНИЕ${NC}\n"; \ + printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"; \ + if [ -d "$(VENV_DIR)" ]; then \ + printf "${YELLOW}⚠️ Обнаружено виртуальное окружение $(VENV_DIR)${NC}\n\n"; \ + REMOVE_VENV=0; \ + if [ -n "$$ACTIVE_VENV" ] && [ "$$ACTIVE_VENV" = "$(CURDIR)/$(VENV_DIR)" ]; then \ + printf "${RED} ⚠️ Это окружение активно в текущей сессии!${NC}\n\n"; \ + printf "${CYAN}Для безопасного удаления:${NC}\n"; \ + printf " 1. Деактивируйте окружение командой: ${GREEN}deactivate${NC}\n"; \ + printf " 2. Затем повторите команду очистки\n\n"; \ + printf "${YELLOW}ВНИМАНИЕ: Удаление активного окружения может вызвать ошибки!${NC}\n"; \ + printf "${RED}Продолжить удаление активного окружения? (y/n): ${NC}"; \ + read choice; \ + if [ "$$choice" = "y" ] || [ "$$choice" = "Y" ]; then \ + REMOVE_VENV=1; \ + printf "\n"; \ + else \ + printf "${YELLOW}✗ Удаление $(VENV_DIR) пропущено${NC}\n\n"; \ + fi; \ + else \ + printf "${RED}Удалить виртуальное окружение? (y/n): ${NC}"; \ + read choice; \ + if [ "$$choice" = "y" ] || [ "$$choice" = "Y" ]; then \ + REMOVE_VENV=1; \ + printf "\n"; \ + else \ + printf "${YELLOW}✗ Удаление $(VENV_DIR) пропущено${NC}\n\n"; \ + fi; \ + fi; \ + if [ "$$REMOVE_VENV" = "1" ]; then \ + printf "${YELLOW}Удаляю $(VENV_DIR)...${NC}\n"; \ + rm -rf $(VENV_DIR); \ + printf "${GREEN}✓ Виртуальное окружение удалено${NC}\n\n"; \ + fi; \ + else \ + printf "${GREEN}✓ Виртуальное окружение не найдено${NC}\n\n"; \ + fi; \ + \ + printf "${YELLOW}🧹 ОЧИСТКА КЭША UV${NC}\n"; \ + printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"; \ + if command -v uv >/dev/null 2>&1; then \ + printf " ${YELLOW}Кэш uv...${NC}\n"; \ + uv cache clean > /dev/null 2>&1 && \ + printf "${GREEN}✓ Кэш uv очищен${NC}\n" || \ + printf "${YELLOW}⚠️ Не удалось очистить кэш uv${NC}\n"; \ + else \ + printf "${YELLOW}⚠️ uv не установлен, пропускаю очистку кэша${NC}\n"; \ + fi; \ + printf "\n"; \ + \ + printf "${YELLOW}⚙️ АВТОМАТИЧЕСКАЯ ОЧИСТКА${NC}\n"; \ + printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"; \ + printf " ${YELLOW}Кэш Python...${NC}\n"; \ + find . -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true; \ + find . -type f -name "*.pyc" -delete 2>/dev/null || true; \ + find . -type f -name "*.pyo" -delete 2>/dev/null || true; \ + printf " ${YELLOW}Кэш инструментов...${NC}\n"; \ + find . -type d -name ".pytest_cache" -exec rm -rf {} + 2>/dev/null || true; \ + find . -type d -name ".mypy_cache" -exec rm -rf {} + 2>/dev/null || true; \ + find . -type d -name ".ruff_cache" -exec rm -rf {} + 2>/dev/null || true; \ + find . -type d -name ".hypothesis" -exec rm -rf {} + 2>/dev/null || true; \ + printf " ${YELLOW}Файлы сборки...${NC}\n"; \ + if [ -d "dist" ]; then rm -rf dist; fi; \ + if [ -d "build" ]; then rm -rf build; fi; \ + if [ -d "${PROJECT_NAME}.egg-info" ]; then rm -rf ${PROJECT_NAME}.egg-info; fi; \ + find . -type d -name "*.egg-info" -exec rm -rf {} + 2>/dev/null || true; \ + printf " ${YELLOW}Отчеты...${NC}\n"; \ + find . -type f -name ".coverage" -delete 2>/dev/null || true; \ + find . -type f -name "coverage.xml" -delete 2>/dev/null || true; \ + find . -type f -name "*.coverage" -delete 2>/dev/null || true; \ + printf " ${YELLOW}Кэш pip...${NC}\n"; \ + rm -rf ~/.cache/pip 2>/dev/null || true; \ + \ + printf "\n${GREEN}✅ ОЧИСТКА ЗАВЕРШЕНА!${NC}\n"; \ + printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n\n"; \ + \ + fi + +# Цель - Информация о проекте +info: + @printf "\n${CYAN}ℹ️ ИНФОРМАЦИЯ О ПРОЕКТЕ ${PROJECT_NAME}${NC}\n" + @printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n" + @printf "${GREEN}Название:${NC} ${PROJECT_NAME}\n" + @printf "${GREEN}Версия:${NC} ${PROJECT_VERSION}\n" + @printf "${GREEN}Автор:${NC} ${PROJECT_AUTHOR_NAME}\n" + @printf "${GREEN}Описание:${NC} ${PROJECT_DESCRIPTION}\n" + @printf "${GREEN}Репозиторий:${NC} ${PROJECT_URL_REPOSITORY}\n" + @printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n" + + @printf "\n${CYAN}🐍ТЕХНИЧЕСКАЯ ИНФОРМАЦИЯ:${NC}\n" + @printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n" + @printf "${GREEN}Зависимости:${NC} ${PROJECT_DEPS}\n" + @printf "${GREEN}Требуемый Python:${NC} >=${PYTHON_REQUIRED}\n" + @printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n" + + @printf "\n${CYAN}📚ПЕРЕД НАЧАЛОМ РАБОТЫ:${NC}\n" + @printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n" + @printf "${YELLOW}Для первого запуска требуется токен ${CYAN}T-Invest API${NC}\n" + @printf "${YELLOW}Получить токен можно в настройках профиля ${CYAN}\\033]8;;https://www.tinkoff.ru/invest/settings/api\\033\\\\Т-Инвестиций\\033]8;;\\033\\\\${NC}\n" + @printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n" + + @printf "\n${CYAN}🚀КОМАНДЫ ДЛЯ НАЧАЛА РАБОТЫ:${NC}\n" + @printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n" + @printf "1. ${YELLOW}make venv${NC} ⚙️ Создать виртуальное окружение\n" + @printf "2. ${YELLOW}make install${NC} 📦Установить ${PROJECT_NAME} и зависимости\n" + @printf "3. ${YELLOW}make run-connect${NC}🔑Выполнить команду и следовать инструкциям\n" + @printf "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n\n" diff --git a/README.md b/README.md index a1e8cfb..d3dc18a 100644 --- a/README.md +++ b/README.md @@ -11,35 +11,219 @@ - Ведение лога отправляемых и получаемых данных от API ### Назначение - - Создание автоматических торговых систем любой сложности - - Написание дополнений к системам Технического Анализа - - Тестирование торговых систем и автоматическая торговля в [BackTrader](https://www.backtrader.com/) через [систему "Финансовая Лаборатория"](https://github.com/cia76/FinLabPy). +- Создание автоматических торговых систем любой сложности +- Написание дополнений к системам Технического Анализа +- Тестирование торговых систем и автоматическая торговля в [BackTrader](https://www.backtrader.com/) через [систему "Финансовая Лаборатория"](https://github.com/cia76/FinLabPy). ### Особенности - Кол-во десятичных знаков в спецификации тикера получаем из шага цены - Время сервера брокера приходит в подписках +- Современная сборка через `pyproject.toml` +- Поддержка Python 3.12+ -### Установка -Установите библиотеку через pip в командной строке: **pip install git+https://github.com/cia76/TinvestPy.git** +--- -### Начало работы -Получить токен можно в настройках профиля Т-Инвестиции [здесь >>>](https://www.tinkoff.ru/invest/settings/) +## 🚀 Два способа использования -Вызовите библиотеку из Python с новым токеном: +### 🔧 Способ 1: Как библиотеку в своих проектах (простая установка) +**Для разработчиков, которые хотят использовать TinvestPy в своих проектах:** +```bash +# Установите библиотеку напрямую из GitHub +pip install git+https://github.com/cia76/TinvestPy.git +# Или укажите в requirements.txt вашего проекта: +git+https://github.com/cia76/TinvestPy.git +``` + +**Пример использования в вашем коде:** +```python +from TinvestPy import TinvestPy + +# При первом использовании передайте токен +tp_provider = TinvestPy('ВАШ_ТОКЕН_ЗДЕСЬ') + +# После первого запуска токен сохранится в защищенном хранилище +# Далее можно использовать без токена: +tp_provider = TinvestPy() + +# Используйте методы библиотеки +accounts = tp_provider.get_accounts() +for account in accounts: + print(f"Счет: {account.id}, Тип: {account.type}") +``` +**Преимущества этого способа:** +- Минимальные действия для начала работы +- Легко добавить в существующие проекты +- Автоматическое управление зависимостями +- Токен сохраняется в защищенном системном хранилище (keyring) + +### 📚 Способ 2: Как проект с примерами (полное клонирование) + +**🪟 Пользователям ОС Windows** + +⚠️ Настоятельно рекомендуется использовать Linux/Ubuntu/MacOS. +Все команды Makefile разработаны и протестированы в Unix-среде. +В Windows возможны проблемы с путями, переносом строк и работой shell-скриптов. +Для лучшего опыта используйте WSL-виртуальную машину или перейдите на Linux/Mac. + +Лучшее решение для Windows — вы получаете полноценный Linux прямо в Windows: +```powershell +# 1. Установите WSL (выполнить в PowerShell от имени Администратора) +wsl --install + +# 2. Перезагрузите компьютер +# 3. Запустите Ubuntu из меню Пуск +# 4. В терминале Ubuntu выполните: +sudo apt update +sudo apt install make python3 python3-pip python3-venv git + +# 5. Смотритре документацию ниже. +``` + +**🐧/🍎 Пользователям ОС Linux/Ubuntu/MacOS** + +**Для тех, кто хочет изучать библиотеку, запускать примеры и модифицировать код:** + ```bash +# 1. Установите UV (современный менеджер Python-пакетов) +curl -LsSf https://astral.sh/uv/install.sh | sh + +# Или через pip (если Python уже установлен) +pip install uv + +# 2. Клонируйте репозиторий +git clone https://github.com/cia76/TinvestPy.git +cd TinvestPy + +# 3. Создайте виртуальное окружение +make venv + + # 4. Активируйте окружение +source .venv/bin/activate + +# 5. Установите пакет и зависимости +make install + +# 6. Выполните команду для установки токена и проверки работы запросов/ответов и подписок +make run-connect + ``` + +**После клонирования вы получите:**: +- Полный исходный код библиотеки +- Папку Examples/ с готовыми примерами +- Makefile для удобной работы +- Возможность модификации кода + +**Запуск примеров:** +```bash +# Все примеры доступны через Makefile +make run-connect # Connect.py (Ввод токена и проверка работы запросов/ответов и подписок) +make run-ticker # Ticker.py (Спецификация тикера) +make run-bars # Bars.py (История тикера) +make run-accounts # Accounts.py (Все торговые счета, позиции и заявки) +make run-stream # Stream.py (Подписка на котировки) +make run-transactions # Transactions.py (Постановка и снятие заявок) + +# Показать все доступные команды +make help +``` +**Преимущества этого способа:** +- Полный доступ ко всем примерам +- Возможность изучать и модифицировать исходный код +- Готовые рабочие примеры для быстрого старта +- Автоматизация через Makefile + +## 🎯 Начало работы + +### Шаг 1: Получение токена +1. Зарегистрируйтесь в [Т-Инвестициях](https://www.tbank.ru/invest/) +2. Получить токен можно в настройках профиля Т-Инвестиции [здесь >>>](https://www.tinkoff.ru/invest/settings/) + +### Шаг 2: Выберите способ использования +**Если выбрали Способ 1 (библиотека):** ```python +# В вашем скрипте from TinvestPy import TinvestPy -tp_provider = TinvestPy('<Токен>') +# Первый запуск - с токеном +provider = TinvestPy('ВАШ_ТОКЕН_ЗДЕСЬ') + +# Последующие запуски - без токена +provider = TinvestPy() +``` + +**Если выбрали Способ 2 (проект с примерами):** +```bash +# 1. Создайте виртуальное окружение +make venv + + # 2. Активируйте окружение +source .venv/bin/activate + +# 3. Установите пакет и зависимости +make install + +# 4. Выполните команду для установки токена и проверки работы запросов/ответов и подписок +make run-connect +``` + +## 📁 Структура проекта +```text +TinvestPy/ +├── pyproject.toml # Конфигурация пакета +├── TinvestPy/ # Исходный код библиотеки +│ ├── __init__.py +│ ├── grpc/ # gRPC файлы +│ └── ... # Остальные модули +├── Examples/ # Примеры использования (только при клонировании) +│ ├── Accounts.py # Все торговые счета, позиции и заявки +│ ├── Bars.py # История тикера +│ ├── Connect.py # Ввод токена и проверка работы запросов/ответов и подписок +│ ├── Stream.py # Подписка на котировки +│ ├── Ticker.py # Спецификация тикера +│ └── Transactions.py # Постановка и снятие заявок +├── Makefile # Утилиты для работы +└── README.md # Документация +``` + +## 🛠️ Команды Makefile (для Способа 2) +```bash +# Основные команды: +make venv # Создать виртуальное окружение +make install # Установить пакет и зависимости +make update # Обновить пакет из GitHub до актуальной версии + +# Запуск примеров: +make run-connect # Connect.py (Ввод токена и проверка работы запросов/ответов и подписок) +make run-ticker # Ticker.py (Спецификация тикера) +make run-bars # Bars.py (История тикера) +make run-accounts # Accounts.py (Все торговые счета, позиции и заявки) +make run-stream # Stream.py (Подписка на котировки) +make run-transactions # Transactions.py (Постановка и снятие заявок) + +# Вспомогательные команды: +make clean # Очистка временных файлов и сборок +make info # Информация о проекте + +# Показать все доступные команды: +make help ``` -Токен сохранится в защищенном системном хранилище. Далее можно вызывать библиотеку без токена: tp_provider = TinvestPy() +### 🔗 Зависимости +**Библиотека использует современный формат pyproject.toml. При установке автоматически подтягиваются:** +- `keyring` - Безопасное хранение торгового токена +- `grpcio` - gRPC для работы с API +- `protobuf` - Работа с Protocol Buffers +- `googleapis-common-protos` - Google API протоколы +- `types-protobuf` - Типы данных для Protocol Buffers -В папке **Examples** находится хорошо документированный код примеров. С них лучше начать разбираться с библиотекой. +**Для Способа 2 (клонирование) также рекомендуется:** +- `uv` - Быстрый менеджер пакетов и окружений (используется в Makefile) -Вопросы по работоспособности T-Invest API задавайте в [официальном Telegram чате Т-Инвестиций здесь >>>](https://t.me/joinchat/VaW05CDzcSdsPULM) +### ❓ Вопросы и поддержка +- Вопросы по работоспособности T-Invest API задавайте в [официальном Telegram чате Т-Инвестиций](https://t.me/joinchat/VaW05CDzcSdsPULM) +- Примеры кода находятся в папке Examples/ (при клонировании репозитория) -### Авторство, право использования, развитие +### 📄 Авторство, право использования, развитие Библиотека написана в рамках проекта [Финансовая Лаборатория](https://finlab.vip/). Авторские и имущественные права на библиотеку принадлежат Чечету Игорю Александровичу. Исправление ошибок, доработка и развитие библиотеки осуществляется автором и сообществом частных алготрейдеров проекта [Финансовая Лаборатория](https://finlab.vip/). @@ -50,7 +234,7 @@ tp_provider = TinvestPy('<Токен>') Для коммерческого внедрения / коммерческого использования необходимо заключить лицензионный договор с автором. Заявку с описанием реализуемого коммерческого проекта можно оставить в форме обратной связи на сайте [Финансовая Лаборатория](https://finlab.vip/). -### Что дальше +### 🚀 Что дальше - Бесплатный курс "Автоторговля" по идеям, концепциям и процессам алгоритмической/автоматической торговли [смотрите здесь >>>](https://finlab.vip/wpm-category/autotrading2021/) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..94a3cdd --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,37 @@ +[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "TinvestPy" +version = "1.42.2026.01.01" +description = "Библиотека-обертка, которая позволяет работать с T-Invest API брокера Т-Инвестиции из Python" +readme = "README.md" +requires-python = ">=3.12" +license = "MIT" +authors = [ + {name = "Чечет Игорь Александрович"} +] +classifiers = [ + "Programming Language :: Python :: 3.12", + "Operating System :: OS Independent", + "Intended Audience :: Developers", + "Topic :: Office/Business :: Financial :: Investment" +] +dependencies = [ + "keyring>=25.7.0", + "grpcio>=1.78.0", + "protobuf>=6.33.5", + "googleapis-common-protos>=1.72.0", + "types-protobuf>=6.32.1.20251210" +] + +[project.urls] +Homepage = "https://github.com/cia76/TinvestPy" +Repository = "https://github.com/cia76/TinvestPy" + +[tool.setuptools] +packages = ["TinvestPy"] + +[tool.setuptools.package-data] +"TinvestPy" = ["grpc/**/*"] \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100644 index da48ae3..0000000 --- a/setup.py +++ /dev/null @@ -1,19 +0,0 @@ -from setuptools import setup, find_packages - -setup(name='TinvestPy', - version='1.42.2026.01.01', - author='Чечет Игорь Александрович', - description='Библиотека-обертка, которая позволяет работать с T-Invest API брокера Т-Инвестиции из Python', - url='https://github.com/cia76/TinvestPy', - packages=find_packages(), - install_requires=[ - 'keyring', # Безопасное хранение торгового токена - 'grpcio', # gRPC - 'protobuf', # proto - 'googleapis-common-protos', # Google API - 'types-protobuf', # Timestamp - ], - python_requires='>=3.12', - package_data={'TinvestPy': ['grpc/**/*']}, # Дополнительно копируем скрипты из папки grpc и вложенных в нее папок - include_package_data=True, # Включаем дополнительные скрипты - ) diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..57d000c --- /dev/null +++ b/uv.lock @@ -0,0 +1,290 @@ +version = 1 +revision = 3 +requires-python = ">=3.12" + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, +] + +[[package]] +name = "cryptography" +version = "46.0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/04/ee2a9e8542e4fa2773b81771ff8349ff19cdd56b7258a0cc442639052edb/cryptography-46.0.5.tar.gz", hash = "sha256:abace499247268e3757271b2f1e244b36b06f8515cf27c4d49468fc9eb16e93d", size = 750064, upload-time = "2026-02-10T19:18:38.255Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/9e/6b4397a3e3d15123de3b1806ef342522393d50736c13b20ec4c9ea6693a6/cryptography-46.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c18ff11e86df2e28854939acde2d003f7984f721eba450b56a200ad90eeb0e6b", size = 4275637, upload-time = "2026-02-10T19:17:10.53Z" }, + { url = "https://files.pythonhosted.org/packages/63/e7/471ab61099a3920b0c77852ea3f0ea611c9702f651600397ac567848b897/cryptography-46.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d7e3d356b8cd4ea5aff04f129d5f66ebdc7b6f8eae802b93739ed520c47c79b", size = 4424742, upload-time = "2026-02-10T19:17:12.388Z" }, + { url = "https://files.pythonhosted.org/packages/37/53/a18500f270342d66bf7e4d9f091114e31e5ee9e7375a5aba2e85a91e0044/cryptography-46.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:50bfb6925eff619c9c023b967d5b77a54e04256c4281b0e21336a130cd7fc263", size = 4277528, upload-time = "2026-02-10T19:17:13.853Z" }, + { url = "https://files.pythonhosted.org/packages/22/29/c2e812ebc38c57b40e7c583895e73c8c5adb4d1e4a0cc4c5a4fdab2b1acc/cryptography-46.0.5-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:803812e111e75d1aa73690d2facc295eaefd4439be1023fefc4995eaea2af90d", size = 4947993, upload-time = "2026-02-10T19:17:15.618Z" }, + { url = "https://files.pythonhosted.org/packages/6b/e7/237155ae19a9023de7e30ec64e5d99a9431a567407ac21170a046d22a5a3/cryptography-46.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ee190460e2fbe447175cda91b88b84ae8322a104fc27766ad09428754a618ed", size = 4456855, upload-time = "2026-02-10T19:17:17.221Z" }, + { url = "https://files.pythonhosted.org/packages/2d/87/fc628a7ad85b81206738abbd213b07702bcbdada1dd43f72236ef3cffbb5/cryptography-46.0.5-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:f145bba11b878005c496e93e257c1e88f154d278d2638e6450d17e0f31e558d2", size = 3984635, upload-time = "2026-02-10T19:17:18.792Z" }, + { url = "https://files.pythonhosted.org/packages/84/29/65b55622bde135aedf4565dc509d99b560ee4095e56989e815f8fd2aa910/cryptography-46.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e9251e3be159d1020c4030bd2e5f84d6a43fe54b6c19c12f51cde9542a2817b2", size = 4277038, upload-time = "2026-02-10T19:17:20.256Z" }, + { url = "https://files.pythonhosted.org/packages/bc/36/45e76c68d7311432741faf1fbf7fac8a196a0a735ca21f504c75d37e2558/cryptography-46.0.5-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:47fb8a66058b80e509c47118ef8a75d14c455e81ac369050f20ba0d23e77fee0", size = 4912181, upload-time = "2026-02-10T19:17:21.825Z" }, + { url = "https://files.pythonhosted.org/packages/6d/1a/c1ba8fead184d6e3d5afcf03d569acac5ad063f3ac9fb7258af158f7e378/cryptography-46.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:4c3341037c136030cb46e4b1e17b7418ea4cbd9dd207e4a6f3b2b24e0d4ac731", size = 4456482, upload-time = "2026-02-10T19:17:25.133Z" }, + { url = "https://files.pythonhosted.org/packages/f9/e5/3fb22e37f66827ced3b902cf895e6a6bc1d095b5b26be26bd13c441fdf19/cryptography-46.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:890bcb4abd5a2d3f852196437129eb3667d62630333aacc13dfd470fad3aaa82", size = 4405497, upload-time = "2026-02-10T19:17:26.66Z" }, + { url = "https://files.pythonhosted.org/packages/1a/df/9d58bb32b1121a8a2f27383fabae4d63080c7ca60b9b5c88be742be04ee7/cryptography-46.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:80a8d7bfdf38f87ca30a5391c0c9ce4ed2926918e017c29ddf643d0ed2778ea1", size = 4667819, upload-time = "2026-02-10T19:17:28.569Z" }, + { url = "https://files.pythonhosted.org/packages/67/c8/581a6702e14f0898a0848105cbefd20c058099e2c2d22ef4e476dfec75d7/cryptography-46.0.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5be7bf2fb40769e05739dd0046e7b26f9d4670badc7b032d6ce4db64dddc0678", size = 4265728, upload-time = "2026-02-10T19:17:35.569Z" }, + { url = "https://files.pythonhosted.org/packages/dd/4a/ba1a65ce8fc65435e5a849558379896c957870dd64fecea97b1ad5f46a37/cryptography-46.0.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fe346b143ff9685e40192a4960938545c699054ba11d4f9029f94751e3f71d87", size = 4408287, upload-time = "2026-02-10T19:17:36.938Z" }, + { url = "https://files.pythonhosted.org/packages/f8/67/8ffdbf7b65ed1ac224d1c2df3943553766914a8ca718747ee3871da6107e/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:c69fd885df7d089548a42d5ec05be26050ebcd2283d89b3d30676eb32ff87dee", size = 4270291, upload-time = "2026-02-10T19:17:38.748Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e5/f52377ee93bc2f2bba55a41a886fd208c15276ffbd2569f2ddc89d50e2c5/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:8293f3dea7fc929ef7240796ba231413afa7b68ce38fd21da2995549f5961981", size = 4927539, upload-time = "2026-02-10T19:17:40.241Z" }, + { url = "https://files.pythonhosted.org/packages/3b/02/cfe39181b02419bbbbcf3abdd16c1c5c8541f03ca8bda240debc467d5a12/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:1abfdb89b41c3be0365328a410baa9df3ff8a9110fb75e7b52e66803ddabc9a9", size = 4442199, upload-time = "2026-02-10T19:17:41.789Z" }, + { url = "https://files.pythonhosted.org/packages/c0/96/2fcaeb4873e536cf71421a388a6c11b5bc846e986b2b069c79363dc1648e/cryptography-46.0.5-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:d66e421495fdb797610a08f43b05269e0a5ea7f5e652a89bfd5a7d3c1dee3648", size = 3960131, upload-time = "2026-02-10T19:17:43.379Z" }, + { url = "https://files.pythonhosted.org/packages/d8/d2/b27631f401ddd644e94c5cf33c9a4069f72011821cf3dc7309546b0642a0/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:4e817a8920bfbcff8940ecfd60f23d01836408242b30f1a708d93198393a80b4", size = 4270072, upload-time = "2026-02-10T19:17:45.481Z" }, + { url = "https://files.pythonhosted.org/packages/f4/a7/60d32b0370dae0b4ebe55ffa10e8599a2a59935b5ece1b9f06edb73abdeb/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:68f68d13f2e1cb95163fa3b4db4bf9a159a418f5f6e7242564fc75fcae667fd0", size = 4892170, upload-time = "2026-02-10T19:17:46.997Z" }, + { url = "https://files.pythonhosted.org/packages/d2/b9/cf73ddf8ef1164330eb0b199a589103c363afa0cf794218c24d524a58eab/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:a3d1fae9863299076f05cb8a778c467578262fae09f9dc0ee9b12eb4268ce663", size = 4441741, upload-time = "2026-02-10T19:17:48.661Z" }, + { url = "https://files.pythonhosted.org/packages/5f/eb/eee00b28c84c726fe8fa0158c65afe312d9c3b78d9d01daf700f1f6e37ff/cryptography-46.0.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4143987a42a2397f2fc3b4d7e3a7d313fbe684f67ff443999e803dd75a76826", size = 4396728, upload-time = "2026-02-10T19:17:50.058Z" }, + { url = "https://files.pythonhosted.org/packages/65/f4/6bc1a9ed5aef7145045114b75b77c2a8261b4d38717bd8dea111a63c3442/cryptography-46.0.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7d731d4b107030987fd61a7f8ab512b25b53cef8f233a97379ede116f30eb67d", size = 4652001, upload-time = "2026-02-10T19:17:51.54Z" }, + { url = "https://files.pythonhosted.org/packages/0f/04/c85bdeab78c8bc77b701bf0d9bdcf514c044e18a46dcff330df5448631b0/cryptography-46.0.5-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1f30a86d2757199cb2d56e48cce14deddf1f9c95f1ef1b64ee91ea43fe2e18", size = 4275349, upload-time = "2026-02-10T19:17:58.419Z" }, + { url = "https://files.pythonhosted.org/packages/5c/32/9b87132a2f91ee7f5223b091dc963055503e9b442c98fc0b8a5ca765fab0/cryptography-46.0.5-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:039917b0dc418bb9f6edce8a906572d69e74bd330b0b3fea4f79dab7f8ddd235", size = 4420667, upload-time = "2026-02-10T19:18:00.619Z" }, + { url = "https://files.pythonhosted.org/packages/a1/a6/a7cb7010bec4b7c5692ca6f024150371b295ee1c108bdc1c400e4c44562b/cryptography-46.0.5-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ba2a27ff02f48193fc4daeadf8ad2590516fa3d0adeeb34336b96f7fa64c1e3a", size = 4276980, upload-time = "2026-02-10T19:18:02.379Z" }, + { url = "https://files.pythonhosted.org/packages/8e/7c/c4f45e0eeff9b91e3f12dbd0e165fcf2a38847288fcfd889deea99fb7b6d/cryptography-46.0.5-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:61aa400dce22cb001a98014f647dc21cda08f7915ceb95df0c9eaf84b4b6af76", size = 4939143, upload-time = "2026-02-10T19:18:03.964Z" }, + { url = "https://files.pythonhosted.org/packages/37/19/e1b8f964a834eddb44fa1b9a9976f4e414cbb7aa62809b6760c8803d22d1/cryptography-46.0.5-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ce58ba46e1bc2aac4f7d9290223cead56743fa6ab94a5d53292ffaac6a91614", size = 4453674, upload-time = "2026-02-10T19:18:05.588Z" }, + { url = "https://files.pythonhosted.org/packages/db/ed/db15d3956f65264ca204625597c410d420e26530c4e2943e05a0d2f24d51/cryptography-46.0.5-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:420d0e909050490d04359e7fdb5ed7e667ca5c3c402b809ae2563d7e66a92229", size = 3978801, upload-time = "2026-02-10T19:18:07.167Z" }, + { url = "https://files.pythonhosted.org/packages/41/e2/df40a31d82df0a70a0daf69791f91dbb70e47644c58581d654879b382d11/cryptography-46.0.5-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:582f5fcd2afa31622f317f80426a027f30dc792e9c80ffee87b993200ea115f1", size = 4276755, upload-time = "2026-02-10T19:18:09.813Z" }, + { url = "https://files.pythonhosted.org/packages/33/45/726809d1176959f4a896b86907b98ff4391a8aa29c0aaaf9450a8a10630e/cryptography-46.0.5-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:bfd56bb4b37ed4f330b82402f6f435845a5f5648edf1ad497da51a8452d5d62d", size = 4901539, upload-time = "2026-02-10T19:18:11.263Z" }, + { url = "https://files.pythonhosted.org/packages/99/0f/a3076874e9c88ecb2ecc31382f6e7c21b428ede6f55aafa1aa272613e3cd/cryptography-46.0.5-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:a3d507bb6a513ca96ba84443226af944b0f7f47dcc9a399d110cd6146481d24c", size = 4452794, upload-time = "2026-02-10T19:18:12.914Z" }, + { url = "https://files.pythonhosted.org/packages/02/ef/ffeb542d3683d24194a38f66ca17c0a4b8bf10631feef44a7ef64e631b1a/cryptography-46.0.5-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9f16fbdf4da055efb21c22d81b89f155f02ba420558db21288b3d0035bafd5f4", size = 4404160, upload-time = "2026-02-10T19:18:14.375Z" }, + { url = "https://files.pythonhosted.org/packages/96/93/682d2b43c1d5f1406ed048f377c0fc9fc8f7b0447a478d5c65ab3d3a66eb/cryptography-46.0.5-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ced80795227d70549a411a4ab66e8ce307899fad2220ce5ab2f296e687eacde9", size = 4667123, upload-time = "2026-02-10T19:18:15.886Z" }, +] + +[[package]] +name = "googleapis-common-protos" +version = "1.72.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e5/7b/adfd75544c415c487b33061fe7ae526165241c1ea133f9a9125a56b39fd8/googleapis_common_protos-1.72.0.tar.gz", hash = "sha256:e55a601c1b32b52d7a3e65f43563e2aa61bcd737998ee672ac9b951cd49319f5", size = 147433, upload-time = "2025-11-06T18:29:24.087Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/ab/09169d5a4612a5f92490806649ac8d41e3ec9129c636754575b3553f4ea4/googleapis_common_protos-1.72.0-py3-none-any.whl", hash = "sha256:4299c5a82d5ae1a9702ada957347726b167f9f8d1fc352477702a1e851ff4038", size = 297515, upload-time = "2025-11-06T18:29:13.14Z" }, +] + +[[package]] +name = "grpcio" +version = "1.78.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/8a/3d098f35c143a89520e568e6539cc098fcd294495910e359889ce8741c84/grpcio-1.78.0.tar.gz", hash = "sha256:7382b95189546f375c174f53a5fa873cef91c4b8005faa05cc5b3beea9c4f1c5", size = 12852416, upload-time = "2026-02-06T09:57:18.093Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/f4/7384ed0178203d6074446b3c4f46c90a22ddf7ae0b3aee521627f54cfc2a/grpcio-1.78.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:f9ab915a267fc47c7e88c387a3a28325b58c898e23d4995f765728f4e3dedb97", size = 5913985, upload-time = "2026-02-06T09:55:26.832Z" }, + { url = "https://files.pythonhosted.org/packages/81/ed/be1caa25f06594463f685b3790b320f18aea49b33166f4141bfdc2bfb236/grpcio-1.78.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3f8904a8165ab21e07e58bf3e30a73f4dffc7a1e0dbc32d51c61b5360d26f43e", size = 11811853, upload-time = "2026-02-06T09:55:29.224Z" }, + { url = "https://files.pythonhosted.org/packages/24/a7/f06d151afc4e64b7e3cc3e872d331d011c279aaab02831e40a81c691fb65/grpcio-1.78.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:859b13906ce098c0b493af92142ad051bf64c7870fa58a123911c88606714996", size = 6475766, upload-time = "2026-02-06T09:55:31.825Z" }, + { url = "https://files.pythonhosted.org/packages/8a/a8/4482922da832ec0082d0f2cc3a10976d84a7424707f25780b82814aafc0a/grpcio-1.78.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b2342d87af32790f934a79c3112641e7b27d63c261b8b4395350dad43eff1dc7", size = 7170027, upload-time = "2026-02-06T09:55:34.7Z" }, + { url = "https://files.pythonhosted.org/packages/54/bf/f4a3b9693e35d25b24b0b39fa46d7d8a3c439e0a3036c3451764678fec20/grpcio-1.78.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:12a771591ae40bc65ba67048fa52ef4f0e6db8279e595fd349f9dfddeef571f9", size = 6690766, upload-time = "2026-02-06T09:55:36.902Z" }, + { url = "https://files.pythonhosted.org/packages/c7/b9/521875265cc99fe5ad4c5a17010018085cae2810a928bf15ebe7d8bcd9cc/grpcio-1.78.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:185dea0d5260cbb2d224c507bf2a5444d5abbb1fa3594c1ed7e4c709d5eb8383", size = 7266161, upload-time = "2026-02-06T09:55:39.824Z" }, + { url = "https://files.pythonhosted.org/packages/05/86/296a82844fd40a4ad4a95f100b55044b4f817dece732bf686aea1a284147/grpcio-1.78.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:51b13f9aed9d59ee389ad666b8c2214cc87b5de258fa712f9ab05f922e3896c6", size = 8253303, upload-time = "2026-02-06T09:55:42.353Z" }, + { url = "https://files.pythonhosted.org/packages/f3/e4/ea3c0caf5468537f27ad5aab92b681ed7cc0ef5f8c9196d3fd42c8c2286b/grpcio-1.78.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fd5f135b1bd58ab088930b3c613455796dfa0393626a6972663ccdda5b4ac6ce", size = 7698222, upload-time = "2026-02-06T09:55:44.629Z" }, + { url = "https://files.pythonhosted.org/packages/d7/47/7f05f81e4bb6b831e93271fb12fd52ba7b319b5402cbc101d588f435df00/grpcio-1.78.0-cp312-cp312-win32.whl", hash = "sha256:94309f498bcc07e5a7d16089ab984d42ad96af1d94b5a4eb966a266d9fcabf68", size = 4066123, upload-time = "2026-02-06T09:55:47.644Z" }, + { url = "https://files.pythonhosted.org/packages/ad/e7/d6914822c88aa2974dbbd10903d801a28a19ce9cd8bad7e694cbbcf61528/grpcio-1.78.0-cp312-cp312-win_amd64.whl", hash = "sha256:9566fe4ababbb2610c39190791e5b829869351d14369603702e890ef3ad2d06e", size = 4797657, upload-time = "2026-02-06T09:55:49.86Z" }, + { url = "https://files.pythonhosted.org/packages/05/a9/8f75894993895f361ed8636cd9237f4ab39ef87fd30db17467235ed1c045/grpcio-1.78.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:ce3a90455492bf8bfa38e56fbbe1dbd4f872a3d8eeaf7337dc3b1c8aa28c271b", size = 5920143, upload-time = "2026-02-06T09:55:52.035Z" }, + { url = "https://files.pythonhosted.org/packages/55/06/0b78408e938ac424100100fd081189451b472236e8a3a1f6500390dc4954/grpcio-1.78.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:2bf5e2e163b356978b23652c4818ce4759d40f4712ee9ec5a83c4be6f8c23a3a", size = 11803926, upload-time = "2026-02-06T09:55:55.494Z" }, + { url = "https://files.pythonhosted.org/packages/88/93/b59fe7832ff6ae3c78b813ea43dac60e295fa03606d14d89d2e0ec29f4f3/grpcio-1.78.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8f2ac84905d12918e4e55a16da17939eb63e433dc11b677267c35568aa63fc84", size = 6478628, upload-time = "2026-02-06T09:55:58.533Z" }, + { url = "https://files.pythonhosted.org/packages/ed/df/e67e3734527f9926b7d9c0dde6cd998d1d26850c3ed8eeec81297967ac67/grpcio-1.78.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b58f37edab4a3881bc6c9bca52670610e0c9ca14e2ea3cf9debf185b870457fb", size = 7173574, upload-time = "2026-02-06T09:56:01.786Z" }, + { url = "https://files.pythonhosted.org/packages/a6/62/cc03fffb07bfba982a9ec097b164e8835546980aec25ecfa5f9c1a47e022/grpcio-1.78.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:735e38e176a88ce41840c21bb49098ab66177c64c82426e24e0082500cc68af5", size = 6692639, upload-time = "2026-02-06T09:56:04.529Z" }, + { url = "https://files.pythonhosted.org/packages/bf/9a/289c32e301b85bdb67d7ec68b752155e674ee3ba2173a1858f118e399ef3/grpcio-1.78.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2045397e63a7a0ee7957c25f7dbb36ddc110e0cfb418403d110c0a7a68a844e9", size = 7268838, upload-time = "2026-02-06T09:56:08.397Z" }, + { url = "https://files.pythonhosted.org/packages/0e/79/1be93f32add280461fa4773880196572563e9c8510861ac2da0ea0f892b6/grpcio-1.78.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a9f136fbafe7ccf4ac7e8e0c28b31066e810be52d6e344ef954a3a70234e1702", size = 8251878, upload-time = "2026-02-06T09:56:10.914Z" }, + { url = "https://files.pythonhosted.org/packages/65/65/793f8e95296ab92e4164593674ae6291b204bb5f67f9d4a711489cd30ffa/grpcio-1.78.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:748b6138585379c737adc08aeffd21222abbda1a86a0dca2a39682feb9196c20", size = 7695412, upload-time = "2026-02-06T09:56:13.593Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9f/1e233fe697ecc82845942c2822ed06bb522e70d6771c28d5528e4c50f6a4/grpcio-1.78.0-cp313-cp313-win32.whl", hash = "sha256:271c73e6e5676afe4fc52907686670c7cea22ab2310b76a59b678403ed40d670", size = 4064899, upload-time = "2026-02-06T09:56:15.601Z" }, + { url = "https://files.pythonhosted.org/packages/4d/27/d86b89e36de8a951501fb06a0f38df19853210f341d0b28f83f4aa0ffa08/grpcio-1.78.0-cp313-cp313-win_amd64.whl", hash = "sha256:f2d4e43ee362adfc05994ed479334d5a451ab7bc3f3fee1b796b8ca66895acb4", size = 4797393, upload-time = "2026-02-06T09:56:17.882Z" }, + { url = "https://files.pythonhosted.org/packages/29/f2/b56e43e3c968bfe822fa6ce5bca10d5c723aa40875b48791ce1029bb78c7/grpcio-1.78.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:e87cbc002b6f440482b3519e36e1313eb5443e9e9e73d6a52d43bd2004fcfd8e", size = 5920591, upload-time = "2026-02-06T09:56:20.758Z" }, + { url = "https://files.pythonhosted.org/packages/5d/81/1f3b65bd30c334167bfa8b0d23300a44e2725ce39bba5b76a2460d85f745/grpcio-1.78.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:c41bc64626db62e72afec66b0c8a0da76491510015417c127bfc53b2fe6d7f7f", size = 11813685, upload-time = "2026-02-06T09:56:24.315Z" }, + { url = "https://files.pythonhosted.org/packages/0e/1c/bbe2f8216a5bd3036119c544d63c2e592bdf4a8ec6e4a1867592f4586b26/grpcio-1.78.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8dfffba826efcf366b1e3ccc37e67afe676f290e13a3b48d31a46739f80a8724", size = 6487803, upload-time = "2026-02-06T09:56:27.367Z" }, + { url = "https://files.pythonhosted.org/packages/16/5c/a6b2419723ea7ddce6308259a55e8e7593d88464ce8db9f4aa857aba96fa/grpcio-1.78.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:74be1268d1439eaaf552c698cdb11cd594f0c49295ae6bb72c34ee31abbe611b", size = 7173206, upload-time = "2026-02-06T09:56:29.876Z" }, + { url = "https://files.pythonhosted.org/packages/df/1e/b8801345629a415ea7e26c83d75eb5dbe91b07ffe5210cc517348a8d4218/grpcio-1.78.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be63c88b32e6c0f1429f1398ca5c09bc64b0d80950c8bb7807d7d7fb36fb84c7", size = 6693826, upload-time = "2026-02-06T09:56:32.305Z" }, + { url = "https://files.pythonhosted.org/packages/34/84/0de28eac0377742679a510784f049738a80424b17287739fc47d63c2439e/grpcio-1.78.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:3c586ac70e855c721bda8f548d38c3ca66ac791dc49b66a8281a1f99db85e452", size = 7277897, upload-time = "2026-02-06T09:56:34.915Z" }, + { url = "https://files.pythonhosted.org/packages/ca/9c/ad8685cfe20559a9edb66f735afdcb2b7d3de69b13666fdfc542e1916ebd/grpcio-1.78.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:35eb275bf1751d2ffbd8f57cdbc46058e857cf3971041521b78b7db94bdaf127", size = 8252404, upload-time = "2026-02-06T09:56:37.553Z" }, + { url = "https://files.pythonhosted.org/packages/3c/05/33a7a4985586f27e1de4803887c417ec7ced145ebd069bc38a9607059e2b/grpcio-1.78.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:207db540302c884b8848036b80db352a832b99dfdf41db1eb554c2c2c7800f65", size = 7696837, upload-time = "2026-02-06T09:56:40.173Z" }, + { url = "https://files.pythonhosted.org/packages/73/77/7382241caf88729b106e49e7d18e3116216c778e6a7e833826eb96de22f7/grpcio-1.78.0-cp314-cp314-win32.whl", hash = "sha256:57bab6deef2f4f1ca76cc04565df38dc5713ae6c17de690721bdf30cb1e0545c", size = 4142439, upload-time = "2026-02-06T09:56:43.258Z" }, + { url = "https://files.pythonhosted.org/packages/48/b2/b096ccce418882fbfda4f7496f9357aaa9a5af1896a9a7f60d9f2b275a06/grpcio-1.78.0-cp314-cp314-win_amd64.whl", hash = "sha256:dce09d6116df20a96acfdbf85e4866258c3758180e8c49845d6ba8248b6d0bbb", size = 4929852, upload-time = "2026-02-06T09:56:45.885Z" }, +] + +[[package]] +name = "jaraco-classes" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "more-itertools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/c0/ed4a27bc5571b99e3cff68f8a9fa5b56ff7df1c2251cc715a652ddd26402/jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", size = 11780, upload-time = "2024-03-31T07:27:36.643Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790", size = 6777, upload-time = "2024-03-31T07:27:34.792Z" }, +] + +[[package]] +name = "jaraco-context" +version = "6.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cb/9c/a788f5bb29c61e456b8ee52ce76dbdd32fd72cd73dd67bc95f42c7a8d13c/jaraco_context-6.1.0.tar.gz", hash = "sha256:129a341b0a85a7db7879e22acd66902fda67882db771754574338898b2d5d86f", size = 15850, upload-time = "2026-01-13T02:53:53.847Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/48/aa685dbf1024c7bd82bede569e3a85f82c32fd3d79ba5fea578f0159571a/jaraco_context-6.1.0-py3-none-any.whl", hash = "sha256:a43b5ed85815223d0d3cfdb6d7ca0d2bc8946f28f30b6f3216bda070f68badda", size = 7065, upload-time = "2026-01-13T02:53:53.031Z" }, +] + +[[package]] +name = "jaraco-functools" +version = "4.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "more-itertools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/27/056e0638a86749374d6f57d0b0db39f29509cce9313cf91bdc0ac4d91084/jaraco_functools-4.4.0.tar.gz", hash = "sha256:da21933b0417b89515562656547a77b4931f98176eb173644c0d35032a33d6bb", size = 19943, upload-time = "2025-12-21T09:29:43.6Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl", hash = "sha256:9eec1e36f45c818d9bf307c8948eb03b2b56cd44087b3cdc989abca1f20b9176", size = 10481, upload-time = "2025-12-21T09:29:42.27Z" }, +] + +[[package]] +name = "jeepney" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/6f/357efd7602486741aa73ffc0617fb310a29b588ed0fd69c2399acbb85b0c/jeepney-0.9.0.tar.gz", hash = "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732", size = 106758, upload-time = "2025-02-27T18:51:01.684Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683", size = 49010, upload-time = "2025-02-27T18:51:00.104Z" }, +] + +[[package]] +name = "keyring" +version = "25.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jaraco-classes" }, + { name = "jaraco-context" }, + { name = "jaraco-functools" }, + { name = "jeepney", marker = "sys_platform == 'linux'" }, + { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, + { name = "secretstorage", marker = "sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/4b/674af6ef2f97d56f0ab5153bf0bfa28ccb6c3ed4d1babf4305449668807b/keyring-25.7.0.tar.gz", hash = "sha256:fe01bd85eb3f8fb3dd0405defdeac9a5b4f6f0439edbb3149577f244a2e8245b", size = 63516, upload-time = "2025-11-16T16:26:09.482Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl", hash = "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f", size = 39160, upload-time = "2025-11-16T16:26:08.402Z" }, +] + +[[package]] +name = "more-itertools" +version = "10.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/5d/38b681d3fce7a266dd9ab73c66959406d565b3e85f21d5e66e1181d93721/more_itertools-10.8.0.tar.gz", hash = "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd", size = 137431, upload-time = "2025-09-02T15:23:11.018Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b", size = 69667, upload-time = "2025-09-02T15:23:09.635Z" }, +] + +[[package]] +name = "protobuf" +version = "6.33.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/25/7c72c307aafc96fa87062aa6291d9f7c94836e43214d43722e86037aac02/protobuf-6.33.5.tar.gz", hash = "sha256:6ddcac2a081f8b7b9642c09406bc6a4290128fce5f471cddd165960bb9119e5c", size = 444465, upload-time = "2026-01-29T21:51:33.494Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/79/af92d0a8369732b027e6d6084251dd8e782c685c72da161bd4a2e00fbabb/protobuf-6.33.5-cp310-abi3-win32.whl", hash = "sha256:d71b040839446bac0f4d162e758bea99c8251161dae9d0983a3b88dee345153b", size = 425769, upload-time = "2026-01-29T21:51:21.751Z" }, + { url = "https://files.pythonhosted.org/packages/55/75/bb9bc917d10e9ee13dee8607eb9ab963b7cf8be607c46e7862c748aa2af7/protobuf-6.33.5-cp310-abi3-win_amd64.whl", hash = "sha256:3093804752167bcab3998bec9f1048baae6e29505adaf1afd14a37bddede533c", size = 437118, upload-time = "2026-01-29T21:51:24.022Z" }, + { url = "https://files.pythonhosted.org/packages/a2/6b/e48dfc1191bc5b52950246275bf4089773e91cb5ba3592621723cdddca62/protobuf-6.33.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a5cb85982d95d906df1e2210e58f8e4f1e3cdc088e52c921a041f9c9a0386de5", size = 427766, upload-time = "2026-01-29T21:51:25.413Z" }, + { url = "https://files.pythonhosted.org/packages/4e/b1/c79468184310de09d75095ed1314b839eb2f72df71097db9d1404a1b2717/protobuf-6.33.5-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:9b71e0281f36f179d00cbcb119cb19dec4d14a81393e5ea220f64b286173e190", size = 324638, upload-time = "2026-01-29T21:51:26.423Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f5/65d838092fd01c44d16037953fd4c2cc851e783de9b8f02b27ec4ffd906f/protobuf-6.33.5-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8afa18e1d6d20af15b417e728e9f60f3aa108ee76f23c3b2c07a2c3b546d3afd", size = 339411, upload-time = "2026-01-29T21:51:27.446Z" }, + { url = "https://files.pythonhosted.org/packages/9b/53/a9443aa3ca9ba8724fdfa02dd1887c1bcd8e89556b715cfbacca6b63dbec/protobuf-6.33.5-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:cbf16ba3350fb7b889fca858fb215967792dc125b35c7976ca4818bee3521cf0", size = 323465, upload-time = "2026-01-29T21:51:28.925Z" }, + { url = "https://files.pythonhosted.org/packages/57/bf/2086963c69bdac3d7cff1cc7ff79b8ce5ea0bec6797a017e1be338a46248/protobuf-6.33.5-py3-none-any.whl", hash = "sha256:69915a973dd0f60f31a08b8318b73eab2bd6a392c79184b3612226b0a3f8ec02", size = 170687, upload-time = "2026-01-29T21:51:32.557Z" }, +] + +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + +[[package]] +name = "pywin32-ctypes" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471, upload-time = "2024-08-14T10:15:34.626Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756, upload-time = "2024-08-14T10:15:33.187Z" }, +] + +[[package]] +name = "secretstorage" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "jeepney" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884, upload-time = "2025-11-23T19:02:53.191Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", size = 15554, upload-time = "2025-11-23T19:02:51.545Z" }, +] + +[[package]] +name = "tinvestpy" +version = "1.42.2026.1.1" +source = { editable = "." } +dependencies = [ + { name = "googleapis-common-protos" }, + { name = "grpcio" }, + { name = "keyring" }, + { name = "protobuf" }, + { name = "types-protobuf" }, +] + +[package.metadata] +requires-dist = [ + { name = "googleapis-common-protos", specifier = ">=1.72.0" }, + { name = "grpcio", specifier = ">=1.78.0" }, + { name = "keyring", specifier = ">=25.7.0" }, + { name = "protobuf", specifier = ">=6.33.5" }, + { name = "types-protobuf", specifier = ">=6.32.1.20251210" }, +] + +[[package]] +name = "types-protobuf" +version = "6.32.1.20251210" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c2/59/c743a842911887cd96d56aa8936522b0cd5f7a7f228c96e81b59fced45be/types_protobuf-6.32.1.20251210.tar.gz", hash = "sha256:c698bb3f020274b1a2798ae09dc773728ce3f75209a35187bd11916ebfde6763", size = 63900, upload-time = "2025-12-10T03:14:25.451Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/aa/43/58e75bac4219cbafee83179505ff44cae3153ec279be0e30583a73b8f108/types_protobuf-6.32.1.20251210-py3-none-any.whl", hash = "sha256:2641f78f3696822a048cfb8d0ff42ccd85c25f12f871fbebe86da63793692140", size = 77921, upload-time = "2025-12-10T03:14:24.477Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +]