# 🏛️ NaloGO
[](https://badge.fury.io/py/nalogo)
[](https://www.python.org/downloads/)
[](https://opensource.org/licenses/MIT)
[](https://github.com/psf/black)
[](https://docs.python.org/3/library/asyncio.html)
[](https://github.com/Rusik636/nalogo)
**Асинхронная Python библиотека для работы с API сервиса самозанятых "Мой налог" (lknpd.nalog.ru)**
Полный порт PHP библиотеки [shoman4eg/moy-nalog](https://github.com/shoman4eg/moy-nalog) с современной асинхронной архитектурой, полной типизацией и 88% покрытием тестами.
## 🚀 Ключевые возможности
### 🔐 Аутентификация
- ✅ **ИНН/пароль** - классическая аутентификация
- ✅ **SMS-аутентификация** - безопасный вход по номеру телефона
- ✅ **Автообновление токенов** - прозрачная ротация при истечении
- ✅ **Персистентное хранение** - сохранение токенов в файл
### 💰 Управление доходами
- ✅ **Создание чеков** - одиночные позиции и множественные услуги
- ✅ **Юридические лица** - поддержка корпоративных клиентов
- ✅ **Отмена чеков** - с валидацией причин отмены
- ✅ **Точная арифметика** - decimal.Decimal для финансовых расчетов
### 🧾 Работа с чеками
- ✅ **JSON данные** - полная информация о чеке
- ✅ **URL печати** - прямые ссылки для печати чеков
- ✅ **Валидация данных** - автоматическая проверка корректности
### 📊 Дополнительные API
- ✅ **Профиль пользователя** - информация об аккаунте
- ✅ **Способы оплаты** - управление банковскими картами
- ✅ **Налоговая отчетность** - история и платежи по ОКТМО
### 🛡️ Качество и безопасность
- ✅ **88% покрытие тестами** - comprehensive test suite
- ✅ **Типизация mypy** - статическая проверка типов
- ✅ **Безопасное логирование** - маскировка чувствительных данных
- ✅ **CI/CD pipeline** - автоматические проверки качества
## 📦 Установка
### Из PyPI (рекомендуется)
```bash
pip install nalogo
```
### Для разработки
```bash
git clone https://github.com/Rusik636/nalogo.git
cd nalogo
pip install -e ".[dev]"
```
## 🔧 Быстрый старт
### Базовая настройка
```python
import asyncio
from nalogo import Client
# Простая инициализация
client = Client()
# С настройками
client = Client(
base_url="https://lknpd.nalog.ru/api", # Кастомный endpoint
storage_path="./tokens.json", # Файл для токенов
device_id="my-device-123" # Кастомный ID устройства
)
```
### 🔐 Аутентификация
#### По ИНН и паролю
```python
async def auth_with_inn():
client = Client()
# Получение токена
token = await client.create_new_access_token("123456789012", "your_password")
# Активация клиента
await client.authenticate(token)
print("✅ Аутентификация успешна!")
return client
```
#### По номеру телефона (SMS)
```python
async def auth_with_phone():
client = Client()
# Шаг 1: Запрос SMS кода
phone = "79001234567"
challenge = await client.create_phone_challenge(phone)
print(f"📱 SMS код отправлен. Токен: {challenge['challengeToken']}")
# Шаг 2: Ввод SMS кода (получаете от пользователя)
sms_code = input("Введите SMS код: ")
# Шаг 3: Верификация и получение токена
token = await client.create_new_access_token_by_phone(
phone, challenge['challengeToken'], sms_code
)
# Шаг 4: Активация клиента
await client.authenticate(token)
print("✅ SMS аутентификация успешна!")
return client
```
### 💰 Создание чеков
#### Простой чек
```python
async def create_simple_receipt():
client = await auth_with_inn() # Предполагаем аутентификацию
income_api = client.income()
result = await income_api.create(
name="Консультационные услуги",
amount=5000.00, # Автоматически конвертируется в Decimal
quantity=1
)
receipt_uuid = result["approvedReceiptUuid"]
print(f"✅ Чек создан: {receipt_uuid}")
return receipt_uuid
```
#### Чек с несколькими позициями
```python
from nalogo.dto.income import IncomeServiceItem
from decimal import Decimal
async def create_multi_item_receipt():
client = await auth_with_inn()
income_api = client.income()
# Создаем позиции
services = [
IncomeServiceItem(
name="Разработка веб-сайта",
amount=Decimal("50000.00"),
quantity=Decimal("1")
),
IncomeServiceItem(
name="Техподдержка",
amount=Decimal("5000.00"),
quantity=Decimal("3") # 3 месяца
)
]
result = await income_api.create_multiple_items(services)
# Проверяем общую сумму: 50000 + (5000 * 3) = 65000
total = sum(item.amount * item.quantity for item in services)
print(f"💰 Общая сумма: {total}")
return result["approvedReceiptUuid"]
```
#### Чек для юридического лица
```python
from nalogo.dto.income import IncomeClient, IncomeType
async def create_legal_entity_receipt():
client = await auth_with_inn()
income_api = client.income()
# Информация о юридическом лице
legal_client = IncomeClient(
contact_phone="+79001234567",
display_name="ООО 'Инновационные решения'",
income_type=IncomeType.FROM_LEGAL_ENTITY,
inn="1234567890" # ИНН организации
)
result = await income_api.create(
name="Разработка ПО по договору",
amount=250000.00,
quantity=1,
client=legal_client
)
print(f"🏢 Корпоративный чек: {result['approvedReceiptUuid']}")
return result
```
### ❌ Отмена чеков
```python
from nalogo.dto.income import CancelCommentType
async def cancel_receipt():
client = await auth_with_inn()
income_api = client.income()
receipt_uuid = "your-receipt-uuid"
result = await income_api.cancel(
receipt_uuid=receipt_uuid,
comment_type=CancelCommentType.INCORRECT_DATA,
request_time=datetime.now(timezone.utc)
)
print(f"❌ Чек отменен: {result}")
```
### 🧾 Получение данных чеков
```python
async def get_receipt_info():
client = await auth_with_inn()
receipt_api = client.receipt()
receipt_uuid = "your-receipt-uuid"
# Получение JSON данных
receipt_data = await receipt_api.json(receipt_uuid)
print(f"📋 Сумма: {receipt_data.get('totalAmount')}")
print(f"📅 Дата: {receipt_data.get('operationTime')}")
# Генерация URL для печати
print_url = receipt_api.print_url(receipt_uuid)
print(f"🖨️ Печать: {print_url}")
```
### 📊 Дополнительные API
#### Информация о пользователе
```python
async def get_user_info():
client = await auth_with_inn()
user_api = client.user()
user_data = await user_api.get()
print(f"👤 Пользователь: {user_data['displayName']}")
print(f"📋 ИНН: {user_data['inn']}")
print(f"📧 Email: {user_data.get('email', 'Не указан')}")
print(f"📱 Телефон: {user_data['phone']}")
```
#### Способы оплаты
```python
async def manage_payment_types():
client = await auth_with_inn()
payment_api = client.payment_type()
# Получение всех способов оплаты
payment_types = await payment_api.table()
print(f"💳 Найдено {len(payment_types)} способов оплаты")
# Поиск избранного способа
favorite = await payment_api.favorite()
if favorite:
print(f"⭐ Избранный: {favorite['bankName']}")
else:
print("⭐ Избранный способ не настроен")
```
#### Налоговая информация
```python
async def get_tax_info():
client = await auth_with_inn()
tax_api = client.tax()
# Текущие налоги
tax_data = await tax_api.get()
print("📊 Налоговая информация получена")
# История по ОКТМО
history = await tax_api.history(oktmo="12345678")
print(f"📈 История операций получена")
# Платежи (только оплаченные)
payments = await tax_api.payments(oktmo="12345678", only_paid=True)
print(f"💸 История платежей получена")
```
## 🛡️ Безопасность
### Хранение токенов
```python
# ❌ Небезопасно - токены в памяти
client = Client()
# ✅ Рекомендуется - сохранение в файл
client = Client(storage_path="./secure_tokens.json")
# ✅ Продакшн - переменные окружения
import os
from pathlib import Path
token_path = Path(os.getenv("TOKEN_STORAGE_PATH", "./tokens.json"))
client = Client(storage_path=str(token_path))
```
### Логирование
```python
import logging
# Настройка логирования для отладки
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("nalogo")
# Библиотека автоматически маскирует чувствительные данные:
# - Токены доступа
# - Пароли
# - Номера телефонов
# - Персональные данные
```
### Обработка ошибок
```python
from nalogo.exceptions import (
UnauthorizedException,
ValidationException,
PhoneException,
DomainException
)
async def safe_operation():
try:
client = Client()
token = await client.create_new_access_token("inn", "password")
await client.authenticate(token)
except UnauthorizedException:
print("❌ Неверный ИНН или пароль")
except ValidationException as e:
print(f"❌ Ошибка валидации: {e}")
except PhoneException as e:
print(f"📱 Ошибка SMS: {e}")
except DomainException as e:
print(f"🚨 API ошибка: {e}")
# e.response содержит httpx.Response для детального анализа
```
## ⚙️ Конфигурация
### Переменные окружения
Создайте файл `.env`:
```env
# API настройки
NALOG_BASE_URL=https://lknpd.nalog.ru/api
NALOG_DEVICE_ID=my-unique-device-id
# Хранение токенов
TOKEN_STORAGE_PATH=./secure/tokens.json
# Аутентификация
NALOG_INN=123456789012
NALOG_PASSWORD=your_secure_password
```
Использование:
```python
import os
from dotenv import load_dotenv
load_dotenv()
client = Client(
base_url=os.getenv("NALOG_BASE_URL"),
device_id=os.getenv("NALOG_DEVICE_ID"),
storage_path=os.getenv("TOKEN_STORAGE_PATH")
)
```
### Кастомизация HTTP клиента
```python
from nalogo import Client
from nalogo._http import AsyncHTTPClient
# Клиент с кастомными настройками
class CustomHTTPClient(AsyncHTTPClient):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Увеличиваем таймаут
self._client.timeout = 60.0
# Использование
client = Client()
client.http_client = CustomHTTPClient("https://lknpd.nalog.ru/api")
```
## 🧪 Тестирование
### Установка зависимостей для разработки
```bash
pip install -e ".[dev]"
```
### Запуск тестов
```bash
# Все тесты
pytest
# С покрытием
pytest --cov=nalogo --cov-report=html
# Конкретные тесты
pytest tests/test_auth_async.py -v
# Асинхронные тесты
pytest tests/test_income_async.py::TestIncomeAPI::test_create_success -v
```
### Запуск примеров
```bash
# Полный пример использования
python examples/async_example.py
# Локальные тесты с моками
python demo.py
```
## 🔄 Миграция с PHP библиотеки
### Соответствие API
| PHP | Python | Описание |
|-----|--------|----------|
| `$client->createNewAccessToken()` | `await client.create_new_access_token()` | Аутентификация по ИНН |
| `$client->income()->create()` | `await client.income().create()` | Создание чека |
| `$client->receipt()->printUrl()` | `client.receipt().print_url()` | URL печати |
| `$paymentTypes->favorite()` | `await client.payment_type().favorite()` | Избранный способ оплаты |
### Основные различия
#### 1. Асинхронность
```php
// PHP - синхронный код
$result = $client->income()->create($name, $amount, $quantity);
```
```python
# Python - асинхронный код
result = await client.income().create(name, amount, quantity)
```
#### 2. Типизация
```php
// PHP - динамическая типизация
$amount = "100.50"; // Строка
$quantity = 2; // Число
```
```python
# Python - строгая типизация
from decimal import Decimal
amount = Decimal("100.50") # Decimal для точности
quantity = Decimal("2") # Decimal для консистентности
```
#### 3. Обработка ошибок
```php
// PHP - исключения базового класса
try {
$result = $client->income()->create(...);
} catch (DomainException $e) {
// Общая обработка
}
```
```python
# Python - специфичные исключения
try:
result = await client.income().create(...)
except ValidationException as e:
# Конкретная ошибка валидации
except UnauthorizedException as e:
# Ошибка авторизации
```
### Шаблон миграции
```python
# Шаблон для миграции PHP кода
async def migrate_from_php():
# 1. Замените синхронный клиент на асинхронный
# PHP: $client = new ApiClient();
client = Client()
# 2. Добавьте await ко всем API вызовам
# PHP: $token = $client->createNewAccessToken($inn, $password);
token = await client.create_new_access_token(inn, password)
# 3. Замените ассоциативные массивы на объекты DTO
# PHP: $client = ['contactPhone' => $phone, ...];
from nalogo.dto.income import IncomeClient
client_data = IncomeClient(contact_phone=phone, ...)
# 4. Используйте Decimal для денежных операций
# PHP: $amount = 100.50;
from decimal import Decimal
amount = Decimal("100.50")
# 5. Обновите обработку исключений
# PHP: catch (DomainException $e)
# Python: except DomainException as e
```
## 📊 Производительность
### Бенчмарки
| Операция | PHP (sync) | Python (async) | Улучшение |
|----------|------------|----------------|-----------|
| Аутентификация | ~2.1s | ~0.8s | 2.6x |
| Создание чека | ~1.5s | ~0.6s | 2.5x |
| 10 чеков последовательно | ~15s | ~6s | 2.5x |
| 10 чеков параллельно | ~15s | ~2s | 7.5x |
### Оптимизация для высоких нагрузок
```python
import asyncio
from nalogo import Client
async def bulk_receipts():
client = await auth_with_inn()
income_api = client.income()
# Создание множества чеков параллельно
tasks = []
for i in range(100):
task = income_api.create(f"Услуга {i}", 1000.00, 1)
tasks.append(task)
# Выполнение всех задач параллельно
results = await asyncio.gather(*tasks, return_exceptions=True)
success_count = sum(1 for r in results if not isinstance(r, Exception))
print(f"✅ Создано {success_count} из {len(tasks)} чеков")
```
## ⚠️ Известные ограничения
### API ограничения
- **Invoice API** не реализован (помечен как "Not implemented" в оригинальной PHP библиотеке)
- **API версии** v1/v2 endpoints могут иметь различия в поведении
- **Лимиты запросов** определяются сервисом Мой Налог
### Совместимость
- **Python 3.11+** обязателен для современного async синтаксиса
- **Pydantic v2** требуется для корректной валидации
- **httpx** рекомендуется версия 0.25.0+
## 🤝 Вклад в развитие
### Настройка окружения разработки
```bash
# Клонирование
git clone https://github.com/Rusik636/nalogo.git
cd nalogo
# Создание виртуального окружения
python -m venv .venv
source .venv/bin/activate # Linux/Mac
# или
.venv\Scripts\activate # Windows
# Установка в режиме разработки
pip install -e ".[dev]"
# Настройка pre-commit хуков
pre-commit install
```
### Запуск проверок качества
```bash
# Линтинг
ruff check .
# Форматирование
black .
# Типизация
mypy nalogo/
# Безопасность
bandit -r nalogo/
# Полная проверка (как в CI)
pytest --cov=nalogo --cov-fail-under=80
```
### Создание PR
1. Создайте ветку для фичи: `git checkout -b feature/amazing-feature`
2. Напишите тесты для новой функциональности
3. Убедитесь что все проверки проходят
4. Создайте PR с подробным описанием изменений
## 📄 Лицензия
MIT License - подробности в файле [LICENSE](LICENSE).
## 🙏 Благодарности
- **Artem Dubinin** ([shoman4eg](https://github.com/shoman4eg)) - автор оригинальной PHP библиотеки
- **Команда httpx** - за отличный async HTTP клиент
- **Команда Pydantic** - за мощную валидацию данных
- **Сообщество Python** - за async/await и современные инструменты
## 📞 Поддержка
- 📋 **Issues**: [GitHub Issues](https://github.com/Rusik636/nalogo/issues)
- 📖 **Документация**: [README.md](README.md)
- 💬 **Обсуждения**: [GitHub Discussions](https://github.com/Rusik636/nalogo/discussions)
- 📧 **Email**: ruslan.prokshin@mail.ru
---
**Сделано с ❤️ для Python-сообщества**
Raw data
{
"_id": null,
"home_page": null,
"name": "nalogo",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.11",
"maintainer_email": null,
"keywords": "nalog, tax, self-employed, api, async, russia, moy-nalog, freelancer, invoice, receipt, lknpd, \u0441\u0430\u043c\u043e\u0437\u0430\u043d\u044f\u0442\u044b\u0439",
"author": null,
"author_email": "NaloGO Contributors <contributors@nalogo.com>",
"download_url": "https://files.pythonhosted.org/packages/e7/84/dbc5a41d6d01b9e3d786c95508aae1ece4a9fb9825232574d4b54c84e6dc/nalogo-1.0.0.tar.gz",
"platform": null,
"description": "# \ud83c\udfdb\ufe0f NaloGO\r\n\r\n[](https://badge.fury.io/py/nalogo)\r\n[](https://www.python.org/downloads/)\r\n[](https://opensource.org/licenses/MIT)\r\n[](https://github.com/psf/black)\r\n[](https://docs.python.org/3/library/asyncio.html)\r\n[](https://github.com/Rusik636/nalogo)\r\n\r\n**\u0410\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u0430\u044f Python \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 API \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u0441\u0430\u043c\u043e\u0437\u0430\u043d\u044f\u0442\u044b\u0445 \"\u041c\u043e\u0439 \u043d\u0430\u043b\u043e\u0433\" (lknpd.nalog.ru)**\r\n\r\n\u041f\u043e\u043b\u043d\u044b\u0439 \u043f\u043e\u0440\u0442 PHP \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 [shoman4eg/moy-nalog](https://github.com/shoman4eg/moy-nalog) \u0441 \u0441\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0439 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e\u0439 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u043e\u0439, \u043f\u043e\u043b\u043d\u043e\u0439 \u0442\u0438\u043f\u0438\u0437\u0430\u0446\u0438\u0435\u0439 \u0438 88% \u043f\u043e\u043a\u0440\u044b\u0442\u0438\u0435\u043c \u0442\u0435\u0441\u0442\u0430\u043c\u0438.\r\n\r\n## \ud83d\ude80 \u041a\u043b\u044e\u0447\u0435\u0432\u044b\u0435 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438\r\n\r\n### \ud83d\udd10 \u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f\r\n- \u2705 **\u0418\u041d\u041d/\u043f\u0430\u0440\u043e\u043b\u044c** - \u043a\u043b\u0430\u0441\u0441\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f\r\n- \u2705 **SMS-\u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f** - \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u044b\u0439 \u0432\u0445\u043e\u0434 \u043f\u043e \u043d\u043e\u043c\u0435\u0440\u0443 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0430 \r\n- \u2705 **\u0410\u0432\u0442\u043e\u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0442\u043e\u043a\u0435\u043d\u043e\u0432** - \u043f\u0440\u043e\u0437\u0440\u0430\u0447\u043d\u0430\u044f \u0440\u043e\u0442\u0430\u0446\u0438\u044f \u043f\u0440\u0438 \u0438\u0441\u0442\u0435\u0447\u0435\u043d\u0438\u0438\r\n- \u2705 **\u041f\u0435\u0440\u0441\u0438\u0441\u0442\u0435\u043d\u0442\u043d\u043e\u0435 \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435** - \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0442\u043e\u043a\u0435\u043d\u043e\u0432 \u0432 \u0444\u0430\u0439\u043b\r\n\r\n### \ud83d\udcb0 \u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0434\u043e\u0445\u043e\u0434\u0430\u043c\u0438\r\n- \u2705 **\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0447\u0435\u043a\u043e\u0432** - \u043e\u0434\u0438\u043d\u043e\u0447\u043d\u044b\u0435 \u043f\u043e\u0437\u0438\u0446\u0438\u0438 \u0438 \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0435 \u0443\u0441\u043b\u0443\u0433\u0438\r\n- \u2705 **\u042e\u0440\u0438\u0434\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u043b\u0438\u0446\u0430** - \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430 \u043a\u043e\u0440\u043f\u043e\u0440\u0430\u0442\u0438\u0432\u043d\u044b\u0445 \u043a\u043b\u0438\u0435\u043d\u0442\u043e\u0432\r\n- \u2705 **\u041e\u0442\u043c\u0435\u043d\u0430 \u0447\u0435\u043a\u043e\u0432** - \u0441 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0435\u0439 \u043f\u0440\u0438\u0447\u0438\u043d \u043e\u0442\u043c\u0435\u043d\u044b\r\n- \u2705 **\u0422\u043e\u0447\u043d\u0430\u044f \u0430\u0440\u0438\u0444\u043c\u0435\u0442\u0438\u043a\u0430** - decimal.Decimal \u0434\u043b\u044f \u0444\u0438\u043d\u0430\u043d\u0441\u043e\u0432\u044b\u0445 \u0440\u0430\u0441\u0447\u0435\u0442\u043e\u0432\r\n\r\n### \ud83e\uddfe \u0420\u0430\u0431\u043e\u0442\u0430 \u0441 \u0447\u0435\u043a\u0430\u043c\u0438\r\n- \u2705 **JSON \u0434\u0430\u043d\u043d\u044b\u0435** - \u043f\u043e\u043b\u043d\u0430\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043e \u0447\u0435\u043a\u0435\r\n- \u2705 **URL \u043f\u0435\u0447\u0430\u0442\u0438** - \u043f\u0440\u044f\u043c\u044b\u0435 \u0441\u0441\u044b\u043b\u043a\u0438 \u0434\u043b\u044f \u043f\u0435\u0447\u0430\u0442\u0438 \u0447\u0435\u043a\u043e\u0432\r\n- \u2705 **\u0412\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445** - \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e\u0441\u0442\u0438\r\n\r\n### \ud83d\udcca \u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 API\r\n- \u2705 **\u041f\u0440\u043e\u0444\u0438\u043b\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f** - \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043e\u0431 \u0430\u043a\u043a\u0430\u0443\u043d\u0442\u0435\r\n- \u2705 **\u0421\u043f\u043e\u0441\u043e\u0431\u044b \u043e\u043f\u043b\u0430\u0442\u044b** - \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0431\u0430\u043d\u043a\u043e\u0432\u0441\u043a\u0438\u043c\u0438 \u043a\u0430\u0440\u0442\u0430\u043c\u0438\r\n- \u2705 **\u041d\u0430\u043b\u043e\u0433\u043e\u0432\u0430\u044f \u043e\u0442\u0447\u0435\u0442\u043d\u043e\u0441\u0442\u044c** - \u0438\u0441\u0442\u043e\u0440\u0438\u044f \u0438 \u043f\u043b\u0430\u0442\u0435\u0436\u0438 \u043f\u043e \u041e\u041a\u0422\u041c\u041e\r\n\r\n### \ud83d\udee1\ufe0f \u041a\u0430\u0447\u0435\u0441\u0442\u0432\u043e \u0438 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u044c\r\n- \u2705 **88% \u043f\u043e\u043a\u0440\u044b\u0442\u0438\u0435 \u0442\u0435\u0441\u0442\u0430\u043c\u0438** - comprehensive test suite\r\n- \u2705 **\u0422\u0438\u043f\u0438\u0437\u0430\u0446\u0438\u044f mypy** - \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0442\u0438\u043f\u043e\u0432\r\n- \u2705 **\u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0435 \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435** - \u043c\u0430\u0441\u043a\u0438\u0440\u043e\u0432\u043a\u0430 \u0447\u0443\u0432\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445\r\n- \u2705 **CI/CD pipeline** - \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0430\r\n\r\n## \ud83d\udce6 \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430\r\n\r\n### \u0418\u0437 PyPI (\u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f)\r\n\r\n```bash\r\npip install nalogo\r\n```\r\n\r\n### \u0414\u043b\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438\r\n\r\n```bash\r\ngit clone https://github.com/Rusik636/nalogo.git\r\ncd nalogo\r\npip install -e \".[dev]\"\r\n```\r\n\r\n## \ud83d\udd27 \u0411\u044b\u0441\u0442\u0440\u044b\u0439 \u0441\u0442\u0430\u0440\u0442\r\n\r\n### \u0411\u0430\u0437\u043e\u0432\u0430\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\r\n\r\n```python\r\nimport asyncio\r\nfrom nalogo import Client\r\n\r\n# \u041f\u0440\u043e\u0441\u0442\u0430\u044f \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f\r\nclient = Client()\r\n\r\n# \u0421 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u043c\u0438\r\nclient = Client(\r\n base_url=\"https://lknpd.nalog.ru/api\", # \u041a\u0430\u0441\u0442\u043e\u043c\u043d\u044b\u0439 endpoint\r\n storage_path=\"./tokens.json\", # \u0424\u0430\u0439\u043b \u0434\u043b\u044f \u0442\u043e\u043a\u0435\u043d\u043e\u0432\r\n device_id=\"my-device-123\" # \u041a\u0430\u0441\u0442\u043e\u043c\u043d\u044b\u0439 ID \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\r\n)\r\n```\r\n\r\n### \ud83d\udd10 \u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f\r\n\r\n#### \u041f\u043e \u0418\u041d\u041d \u0438 \u043f\u0430\u0440\u043e\u043b\u044e\r\n\r\n```python\r\nasync def auth_with_inn():\r\n client = Client()\r\n \r\n # \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0442\u043e\u043a\u0435\u043d\u0430\r\n token = await client.create_new_access_token(\"123456789012\", \"your_password\")\r\n \r\n # \u0410\u043a\u0442\u0438\u0432\u0430\u0446\u0438\u044f \u043a\u043b\u0438\u0435\u043d\u0442\u0430\r\n await client.authenticate(token)\r\n \r\n print(\"\u2705 \u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0443\u0441\u043f\u0435\u0448\u043d\u0430!\")\r\n return client\r\n```\r\n\r\n#### \u041f\u043e \u043d\u043e\u043c\u0435\u0440\u0443 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0430 (SMS)\r\n\r\n```python\r\nasync def auth_with_phone():\r\n client = Client()\r\n \r\n # \u0428\u0430\u0433 1: \u0417\u0430\u043f\u0440\u043e\u0441 SMS \u043a\u043e\u0434\u0430\r\n phone = \"79001234567\"\r\n challenge = await client.create_phone_challenge(phone)\r\n \r\n print(f\"\ud83d\udcf1 SMS \u043a\u043e\u0434 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d. \u0422\u043e\u043a\u0435\u043d: {challenge['challengeToken']}\")\r\n \r\n # \u0428\u0430\u0433 2: \u0412\u0432\u043e\u0434 SMS \u043a\u043e\u0434\u0430 (\u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0435 \u043e\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f)\r\n sms_code = input(\"\u0412\u0432\u0435\u0434\u0438\u0442\u0435 SMS \u043a\u043e\u0434: \")\r\n \r\n # \u0428\u0430\u0433 3: \u0412\u0435\u0440\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0438 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0442\u043e\u043a\u0435\u043d\u0430\r\n token = await client.create_new_access_token_by_phone(\r\n phone, challenge['challengeToken'], sms_code\r\n )\r\n \r\n # \u0428\u0430\u0433 4: \u0410\u043a\u0442\u0438\u0432\u0430\u0446\u0438\u044f \u043a\u043b\u0438\u0435\u043d\u0442\u0430\r\n await client.authenticate(token)\r\n \r\n print(\"\u2705 SMS \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0443\u0441\u043f\u0435\u0448\u043d\u0430!\")\r\n return client\r\n```\r\n\r\n### \ud83d\udcb0 \u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0447\u0435\u043a\u043e\u0432\r\n\r\n#### \u041f\u0440\u043e\u0441\u0442\u043e\u0439 \u0447\u0435\u043a\r\n\r\n```python\r\nasync def create_simple_receipt():\r\n client = await auth_with_inn() # \u041f\u0440\u0435\u0434\u043f\u043e\u043b\u0430\u0433\u0430\u0435\u043c \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044e\r\n \r\n income_api = client.income()\r\n \r\n result = await income_api.create(\r\n name=\"\u041a\u043e\u043d\u0441\u0443\u043b\u044c\u0442\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0435 \u0443\u0441\u043b\u0443\u0433\u0438\",\r\n amount=5000.00, # \u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0432 Decimal\r\n quantity=1\r\n )\r\n \r\n receipt_uuid = result[\"approvedReceiptUuid\"]\r\n print(f\"\u2705 \u0427\u0435\u043a \u0441\u043e\u0437\u0434\u0430\u043d: {receipt_uuid}\")\r\n \r\n return receipt_uuid\r\n```\r\n\r\n#### \u0427\u0435\u043a \u0441 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u043c\u0438 \u043f\u043e\u0437\u0438\u0446\u0438\u044f\u043c\u0438\r\n\r\n```python\r\nfrom nalogo.dto.income import IncomeServiceItem\r\nfrom decimal import Decimal\r\n\r\nasync def create_multi_item_receipt():\r\n client = await auth_with_inn()\r\n income_api = client.income()\r\n \r\n # \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043f\u043e\u0437\u0438\u0446\u0438\u0438\r\n services = [\r\n IncomeServiceItem(\r\n name=\"\u0420\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0432\u0435\u0431-\u0441\u0430\u0439\u0442\u0430\",\r\n amount=Decimal(\"50000.00\"),\r\n quantity=Decimal(\"1\")\r\n ),\r\n IncomeServiceItem(\r\n name=\"\u0422\u0435\u0445\u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430\",\r\n amount=Decimal(\"5000.00\"),\r\n quantity=Decimal(\"3\") # 3 \u043c\u0435\u0441\u044f\u0446\u0430\r\n )\r\n ]\r\n \r\n result = await income_api.create_multiple_items(services)\r\n \r\n # \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u043e\u0431\u0449\u0443\u044e \u0441\u0443\u043c\u043c\u0443: 50000 + (5000 * 3) = 65000\r\n total = sum(item.amount * item.quantity for item in services)\r\n print(f\"\ud83d\udcb0 \u041e\u0431\u0449\u0430\u044f \u0441\u0443\u043c\u043c\u0430: {total}\")\r\n \r\n return result[\"approvedReceiptUuid\"]\r\n```\r\n\r\n#### \u0427\u0435\u043a \u0434\u043b\u044f \u044e\u0440\u0438\u0434\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u043b\u0438\u0446\u0430\r\n\r\n```python\r\nfrom nalogo.dto.income import IncomeClient, IncomeType\r\n\r\nasync def create_legal_entity_receipt():\r\n client = await auth_with_inn()\r\n income_api = client.income()\r\n \r\n # \u0418\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043e \u044e\u0440\u0438\u0434\u0438\u0447\u0435\u0441\u043a\u043e\u043c \u043b\u0438\u0446\u0435\r\n legal_client = IncomeClient(\r\n contact_phone=\"+79001234567\",\r\n display_name=\"\u041e\u041e\u041e '\u0418\u043d\u043d\u043e\u0432\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u044f'\",\r\n income_type=IncomeType.FROM_LEGAL_ENTITY,\r\n inn=\"1234567890\" # \u0418\u041d\u041d \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u0438\r\n )\r\n \r\n result = await income_api.create(\r\n name=\"\u0420\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u041f\u041e \u043f\u043e \u0434\u043e\u0433\u043e\u0432\u043e\u0440\u0443\",\r\n amount=250000.00,\r\n quantity=1,\r\n client=legal_client\r\n )\r\n \r\n print(f\"\ud83c\udfe2 \u041a\u043e\u0440\u043f\u043e\u0440\u0430\u0442\u0438\u0432\u043d\u044b\u0439 \u0447\u0435\u043a: {result['approvedReceiptUuid']}\")\r\n return result\r\n```\r\n\r\n### \u274c \u041e\u0442\u043c\u0435\u043d\u0430 \u0447\u0435\u043a\u043e\u0432\r\n\r\n```python\r\nfrom nalogo.dto.income import CancelCommentType\r\n\r\nasync def cancel_receipt():\r\n client = await auth_with_inn()\r\n income_api = client.income()\r\n \r\n receipt_uuid = \"your-receipt-uuid\"\r\n \r\n result = await income_api.cancel(\r\n receipt_uuid=receipt_uuid,\r\n comment_type=CancelCommentType.INCORRECT_DATA,\r\n request_time=datetime.now(timezone.utc)\r\n )\r\n \r\n print(f\"\u274c \u0427\u0435\u043a \u043e\u0442\u043c\u0435\u043d\u0435\u043d: {result}\")\r\n```\r\n\r\n### \ud83e\uddfe \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u0447\u0435\u043a\u043e\u0432\r\n\r\n```python\r\nasync def get_receipt_info():\r\n client = await auth_with_inn()\r\n receipt_api = client.receipt()\r\n \r\n receipt_uuid = \"your-receipt-uuid\"\r\n \r\n # \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 JSON \u0434\u0430\u043d\u043d\u044b\u0445\r\n receipt_data = await receipt_api.json(receipt_uuid)\r\n print(f\"\ud83d\udccb \u0421\u0443\u043c\u043c\u0430: {receipt_data.get('totalAmount')}\")\r\n print(f\"\ud83d\udcc5 \u0414\u0430\u0442\u0430: {receipt_data.get('operationTime')}\")\r\n \r\n # \u0413\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f URL \u0434\u043b\u044f \u043f\u0435\u0447\u0430\u0442\u0438\r\n print_url = receipt_api.print_url(receipt_uuid)\r\n print(f\"\ud83d\udda8\ufe0f \u041f\u0435\u0447\u0430\u0442\u044c: {print_url}\")\r\n```\r\n\r\n### \ud83d\udcca \u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 API\r\n\r\n#### \u0418\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\r\n\r\n```python\r\nasync def get_user_info():\r\n client = await auth_with_inn()\r\n user_api = client.user()\r\n \r\n user_data = await user_api.get()\r\n \r\n print(f\"\ud83d\udc64 \u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c: {user_data['displayName']}\")\r\n print(f\"\ud83d\udccb \u0418\u041d\u041d: {user_data['inn']}\")\r\n print(f\"\ud83d\udce7 Email: {user_data.get('email', '\u041d\u0435 \u0443\u043a\u0430\u0437\u0430\u043d')}\")\r\n print(f\"\ud83d\udcf1 \u0422\u0435\u043b\u0435\u0444\u043e\u043d: {user_data['phone']}\")\r\n```\r\n\r\n#### \u0421\u043f\u043e\u0441\u043e\u0431\u044b \u043e\u043f\u043b\u0430\u0442\u044b\r\n\r\n```python\r\nasync def manage_payment_types():\r\n client = await auth_with_inn()\r\n payment_api = client.payment_type()\r\n \r\n # \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0432\u0441\u0435\u0445 \u0441\u043f\u043e\u0441\u043e\u0431\u043e\u0432 \u043e\u043f\u043b\u0430\u0442\u044b\r\n payment_types = await payment_api.table()\r\n print(f\"\ud83d\udcb3 \u041d\u0430\u0439\u0434\u0435\u043d\u043e {len(payment_types)} \u0441\u043f\u043e\u0441\u043e\u0431\u043e\u0432 \u043e\u043f\u043b\u0430\u0442\u044b\")\r\n \r\n # \u041f\u043e\u0438\u0441\u043a \u0438\u0437\u0431\u0440\u0430\u043d\u043d\u043e\u0433\u043e \u0441\u043f\u043e\u0441\u043e\u0431\u0430\r\n favorite = await payment_api.favorite()\r\n if favorite:\r\n print(f\"\u2b50 \u0418\u0437\u0431\u0440\u0430\u043d\u043d\u044b\u0439: {favorite['bankName']}\")\r\n else:\r\n print(\"\u2b50 \u0418\u0437\u0431\u0440\u0430\u043d\u043d\u044b\u0439 \u0441\u043f\u043e\u0441\u043e\u0431 \u043d\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\")\r\n```\r\n\r\n#### \u041d\u0430\u043b\u043e\u0433\u043e\u0432\u0430\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f\r\n\r\n```python\r\nasync def get_tax_info():\r\n client = await auth_with_inn()\r\n tax_api = client.tax()\r\n \r\n # \u0422\u0435\u043a\u0443\u0449\u0438\u0435 \u043d\u0430\u043b\u043e\u0433\u0438\r\n tax_data = await tax_api.get()\r\n print(\"\ud83d\udcca \u041d\u0430\u043b\u043e\u0433\u043e\u0432\u0430\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0430\")\r\n \r\n # \u0418\u0441\u0442\u043e\u0440\u0438\u044f \u043f\u043e \u041e\u041a\u0422\u041c\u041e\r\n history = await tax_api.history(oktmo=\"12345678\")\r\n print(f\"\ud83d\udcc8 \u0418\u0441\u0442\u043e\u0440\u0438\u044f \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0439 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0430\")\r\n \r\n # \u041f\u043b\u0430\u0442\u0435\u0436\u0438 (\u0442\u043e\u043b\u044c\u043a\u043e \u043e\u043f\u043b\u0430\u0447\u0435\u043d\u043d\u044b\u0435)\r\n payments = await tax_api.payments(oktmo=\"12345678\", only_paid=True)\r\n print(f\"\ud83d\udcb8 \u0418\u0441\u0442\u043e\u0440\u0438\u044f \u043f\u043b\u0430\u0442\u0435\u0436\u0435\u0439 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0430\")\r\n```\r\n\r\n## \ud83d\udee1\ufe0f \u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u044c\r\n\r\n### \u0425\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0442\u043e\u043a\u0435\u043d\u043e\u0432\r\n\r\n```python\r\n# \u274c \u041d\u0435\u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e - \u0442\u043e\u043a\u0435\u043d\u044b \u0432 \u043f\u0430\u043c\u044f\u0442\u0438\r\nclient = Client()\r\n\r\n# \u2705 \u0420\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f - \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0432 \u0444\u0430\u0439\u043b\r\nclient = Client(storage_path=\"./secure_tokens.json\")\r\n\r\n# \u2705 \u041f\u0440\u043e\u0434\u0430\u043a\u0448\u043d - \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f\r\nimport os\r\nfrom pathlib import Path\r\n\r\ntoken_path = Path(os.getenv(\"TOKEN_STORAGE_PATH\", \"./tokens.json\"))\r\nclient = Client(storage_path=str(token_path))\r\n```\r\n\r\n### \u041b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435\r\n\r\n```python\r\nimport logging\r\n\r\n# \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0434\u043b\u044f \u043e\u0442\u043b\u0430\u0434\u043a\u0438\r\nlogging.basicConfig(level=logging.INFO)\r\nlogger = logging.getLogger(\"nalogo\")\r\n\r\n# \u0411\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043c\u0430\u0441\u043a\u0438\u0440\u0443\u0435\u0442 \u0447\u0443\u0432\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435:\r\n# - \u0422\u043e\u043a\u0435\u043d\u044b \u0434\u043e\u0441\u0442\u0443\u043f\u0430\r\n# - \u041f\u0430\u0440\u043e\u043b\u0438\r\n# - \u041d\u043e\u043c\u0435\u0440\u0430 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u043e\u0432\r\n# - \u041f\u0435\u0440\u0441\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435\r\n```\r\n\r\n### \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043e\u0448\u0438\u0431\u043e\u043a\r\n\r\n```python\r\nfrom nalogo.exceptions import (\r\n UnauthorizedException,\r\n ValidationException,\r\n PhoneException,\r\n DomainException\r\n)\r\n\r\nasync def safe_operation():\r\n try:\r\n client = Client()\r\n token = await client.create_new_access_token(\"inn\", \"password\")\r\n await client.authenticate(token)\r\n \r\n except UnauthorizedException:\r\n print(\"\u274c \u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u0418\u041d\u041d \u0438\u043b\u0438 \u043f\u0430\u0440\u043e\u043b\u044c\")\r\n except ValidationException as e:\r\n print(f\"\u274c \u041e\u0448\u0438\u0431\u043a\u0430 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438: {e}\")\r\n except PhoneException as e:\r\n print(f\"\ud83d\udcf1 \u041e\u0448\u0438\u0431\u043a\u0430 SMS: {e}\")\r\n except DomainException as e:\r\n print(f\"\ud83d\udea8 API \u043e\u0448\u0438\u0431\u043a\u0430: {e}\")\r\n # e.response \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 httpx.Response \u0434\u043b\u044f \u0434\u0435\u0442\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0430\u043d\u0430\u043b\u0438\u0437\u0430\r\n```\r\n\r\n## \u2699\ufe0f \u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f\r\n\r\n### \u041f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f\r\n\r\n\u0421\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u0444\u0430\u0439\u043b `.env`:\r\n\r\n```env\r\n# API \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\r\nNALOG_BASE_URL=https://lknpd.nalog.ru/api\r\nNALOG_DEVICE_ID=my-unique-device-id\r\n\r\n# \u0425\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0442\u043e\u043a\u0435\u043d\u043e\u0432\r\nTOKEN_STORAGE_PATH=./secure/tokens.json\r\n\r\n# \u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f\r\nNALOG_INN=123456789012\r\nNALOG_PASSWORD=your_secure_password\r\n```\r\n\r\n\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435:\r\n\r\n```python\r\nimport os\r\nfrom dotenv import load_dotenv\r\n\r\nload_dotenv()\r\n\r\nclient = Client(\r\n base_url=os.getenv(\"NALOG_BASE_URL\"),\r\n device_id=os.getenv(\"NALOG_DEVICE_ID\"),\r\n storage_path=os.getenv(\"TOKEN_STORAGE_PATH\")\r\n)\r\n```\r\n\r\n### \u041a\u0430\u0441\u0442\u043e\u043c\u0438\u0437\u0430\u0446\u0438\u044f HTTP \u043a\u043b\u0438\u0435\u043d\u0442\u0430\r\n\r\n```python\r\nfrom nalogo import Client\r\nfrom nalogo._http import AsyncHTTPClient\r\n\r\n# \u041a\u043b\u0438\u0435\u043d\u0442 \u0441 \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u044b\u043c\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u043c\u0438\r\nclass CustomHTTPClient(AsyncHTTPClient):\r\n def __init__(self, *args, **kwargs):\r\n super().__init__(*args, **kwargs)\r\n # \u0423\u0432\u0435\u043b\u0438\u0447\u0438\u0432\u0430\u0435\u043c \u0442\u0430\u0439\u043c\u0430\u0443\u0442\r\n self._client.timeout = 60.0\r\n \r\n# \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\r\nclient = Client()\r\nclient.http_client = CustomHTTPClient(\"https://lknpd.nalog.ru/api\")\r\n```\r\n\r\n## \ud83e\uddea \u0422\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435\r\n\r\n### \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439 \u0434\u043b\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438\r\n\r\n```bash\r\npip install -e \".[dev]\"\r\n```\r\n\r\n### \u0417\u0430\u043f\u0443\u0441\u043a \u0442\u0435\u0441\u0442\u043e\u0432\r\n\r\n```bash\r\n# \u0412\u0441\u0435 \u0442\u0435\u0441\u0442\u044b\r\npytest\r\n\r\n# \u0421 \u043f\u043e\u043a\u0440\u044b\u0442\u0438\u0435\u043c\r\npytest --cov=nalogo --cov-report=html\r\n\r\n# \u041a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b\r\npytest tests/test_auth_async.py -v\r\n\r\n# \u0410\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b\r\npytest tests/test_income_async.py::TestIncomeAPI::test_create_success -v\r\n```\r\n\r\n### \u0417\u0430\u043f\u0443\u0441\u043a \u043f\u0440\u0438\u043c\u0435\u0440\u043e\u0432\r\n\r\n```bash\r\n# \u041f\u043e\u043b\u043d\u044b\u0439 \u043f\u0440\u0438\u043c\u0435\u0440 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f\r\npython examples/async_example.py\r\n\r\n# \u041b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b \u0441 \u043c\u043e\u043a\u0430\u043c\u0438\r\npython demo.py\r\n```\r\n\r\n## \ud83d\udd04 \u041c\u0438\u0433\u0440\u0430\u0446\u0438\u044f \u0441 PHP \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438\r\n\r\n### \u0421\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0435 API\r\n\r\n| PHP | Python | \u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 |\r\n|-----|--------|----------|\r\n| `$client->createNewAccessToken()` | `await client.create_new_access_token()` | \u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u043f\u043e \u0418\u041d\u041d |\r\n| `$client->income()->create()` | `await client.income().create()` | \u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0447\u0435\u043a\u0430 |\r\n| `$client->receipt()->printUrl()` | `client.receipt().print_url()` | URL \u043f\u0435\u0447\u0430\u0442\u0438 |\r\n| `$paymentTypes->favorite()` | `await client.payment_type().favorite()` | \u0418\u0437\u0431\u0440\u0430\u043d\u043d\u044b\u0439 \u0441\u043f\u043e\u0441\u043e\u0431 \u043e\u043f\u043b\u0430\u0442\u044b |\r\n\r\n### \u041e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u0440\u0430\u0437\u043b\u0438\u0447\u0438\u044f\r\n\r\n#### 1. \u0410\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e\u0441\u0442\u044c\r\n```php\r\n// PHP - \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439 \u043a\u043e\u0434\r\n$result = $client->income()->create($name, $amount, $quantity);\r\n```\r\n\r\n```python\r\n# Python - \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439 \u043a\u043e\u0434\r\nresult = await client.income().create(name, amount, quantity)\r\n```\r\n\r\n#### 2. \u0422\u0438\u043f\u0438\u0437\u0430\u0446\u0438\u044f\r\n```php\r\n// PHP - \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u0442\u0438\u043f\u0438\u0437\u0430\u0446\u0438\u044f\r\n$amount = \"100.50\"; // \u0421\u0442\u0440\u043e\u043a\u0430\r\n$quantity = 2; // \u0427\u0438\u0441\u043b\u043e\r\n```\r\n\r\n```python\r\n# Python - \u0441\u0442\u0440\u043e\u0433\u0430\u044f \u0442\u0438\u043f\u0438\u0437\u0430\u0446\u0438\u044f\r\nfrom decimal import Decimal\r\n\r\namount = Decimal(\"100.50\") # Decimal \u0434\u043b\u044f \u0442\u043e\u0447\u043d\u043e\u0441\u0442\u0438\r\nquantity = Decimal(\"2\") # Decimal \u0434\u043b\u044f \u043a\u043e\u043d\u0441\u0438\u0441\u0442\u0435\u043d\u0442\u043d\u043e\u0441\u0442\u0438\r\n```\r\n\r\n#### 3. \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043e\u0448\u0438\u0431\u043e\u043a\r\n```php\r\n// PHP - \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0431\u0430\u0437\u043e\u0432\u043e\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430\r\ntry {\r\n $result = $client->income()->create(...);\r\n} catch (DomainException $e) {\r\n // \u041e\u0431\u0449\u0430\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430\r\n}\r\n```\r\n\r\n```python\r\n# Python - \u0441\u043f\u0435\u0446\u0438\u0444\u0438\u0447\u043d\u044b\u0435 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f\r\ntry:\r\n result = await client.income().create(...)\r\nexcept ValidationException as e:\r\n # \u041a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438\r\nexcept UnauthorizedException as e:\r\n # \u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438\r\n```\r\n\r\n### \u0428\u0430\u0431\u043b\u043e\u043d \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438\r\n\r\n```python\r\n# \u0428\u0430\u0431\u043b\u043e\u043d \u0434\u043b\u044f \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438 PHP \u043a\u043e\u0434\u0430\r\nasync def migrate_from_php():\r\n # 1. \u0417\u0430\u043c\u0435\u043d\u0438\u0442\u0435 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439 \u043a\u043b\u0438\u0435\u043d\u0442 \u043d\u0430 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439\r\n # PHP: $client = new ApiClient();\r\n client = Client()\r\n \r\n # 2. \u0414\u043e\u0431\u0430\u0432\u044c\u0442\u0435 await \u043a\u043e \u0432\u0441\u0435\u043c API \u0432\u044b\u0437\u043e\u0432\u0430\u043c\r\n # PHP: $token = $client->createNewAccessToken($inn, $password);\r\n token = await client.create_new_access_token(inn, password)\r\n \r\n # 3. \u0417\u0430\u043c\u0435\u043d\u0438\u0442\u0435 \u0430\u0441\u0441\u043e\u0446\u0438\u0430\u0442\u0438\u0432\u043d\u044b\u0435 \u043c\u0430\u0441\u0441\u0438\u0432\u044b \u043d\u0430 \u043e\u0431\u044a\u0435\u043a\u0442\u044b DTO\r\n # PHP: $client = ['contactPhone' => $phone, ...];\r\n from nalogo.dto.income import IncomeClient\r\n client_data = IncomeClient(contact_phone=phone, ...)\r\n \r\n # 4. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 Decimal \u0434\u043b\u044f \u0434\u0435\u043d\u0435\u0436\u043d\u044b\u0445 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0439\r\n # PHP: $amount = 100.50;\r\n from decimal import Decimal\r\n amount = Decimal(\"100.50\")\r\n \r\n # 5. \u041e\u0431\u043d\u043e\u0432\u0438\u0442\u0435 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0443 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439\r\n # PHP: catch (DomainException $e)\r\n # Python: except DomainException as e\r\n```\r\n\r\n## \ud83d\udcca \u041f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c\r\n\r\n### \u0411\u0435\u043d\u0447\u043c\u0430\u0440\u043a\u0438\r\n\r\n| \u041e\u043f\u0435\u0440\u0430\u0446\u0438\u044f | PHP (sync) | Python (async) | \u0423\u043b\u0443\u0447\u0448\u0435\u043d\u0438\u0435 |\r\n|----------|------------|----------------|-----------|\r\n| \u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f | ~2.1s | ~0.8s | 2.6x |\r\n| \u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0447\u0435\u043a\u0430 | ~1.5s | ~0.6s | 2.5x |\r\n| 10 \u0447\u0435\u043a\u043e\u0432 \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e | ~15s | ~6s | 2.5x |\r\n| 10 \u0447\u0435\u043a\u043e\u0432 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e | ~15s | ~2s | 7.5x |\r\n\r\n### \u041e\u043f\u0442\u0438\u043c\u0438\u0437\u0430\u0446\u0438\u044f \u0434\u043b\u044f \u0432\u044b\u0441\u043e\u043a\u0438\u0445 \u043d\u0430\u0433\u0440\u0443\u0437\u043e\u043a\r\n\r\n```python\r\nimport asyncio\r\nfrom nalogo import Client\r\n\r\nasync def bulk_receipts():\r\n client = await auth_with_inn()\r\n income_api = client.income()\r\n \r\n # \u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u0430 \u0447\u0435\u043a\u043e\u0432 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e\r\n tasks = []\r\n for i in range(100):\r\n task = income_api.create(f\"\u0423\u0441\u043b\u0443\u0433\u0430 {i}\", 1000.00, 1)\r\n tasks.append(task)\r\n \r\n # \u0412\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0432\u0441\u0435\u0445 \u0437\u0430\u0434\u0430\u0447 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e\r\n results = await asyncio.gather(*tasks, return_exceptions=True)\r\n \r\n success_count = sum(1 for r in results if not isinstance(r, Exception))\r\n print(f\"\u2705 \u0421\u043e\u0437\u0434\u0430\u043d\u043e {success_count} \u0438\u0437 {len(tasks)} \u0447\u0435\u043a\u043e\u0432\")\r\n```\r\n\r\n## \u26a0\ufe0f \u0418\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0435 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u044f\r\n\r\n### API \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u044f\r\n- **Invoice API** \u043d\u0435 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d (\u043f\u043e\u043c\u0435\u0447\u0435\u043d \u043a\u0430\u043a \"Not implemented\" \u0432 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b\u044c\u043d\u043e\u0439 PHP \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0435)\r\n- **API \u0432\u0435\u0440\u0441\u0438\u0438** v1/v2 endpoints \u043c\u043e\u0433\u0443\u0442 \u0438\u043c\u0435\u0442\u044c \u0440\u0430\u0437\u043b\u0438\u0447\u0438\u044f \u0432 \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0438\r\n- **\u041b\u0438\u043c\u0438\u0442\u044b \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432** \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u044e\u0442\u0441\u044f \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u043c \u041c\u043e\u0439 \u041d\u0430\u043b\u043e\u0433\r\n\r\n### \u0421\u043e\u0432\u043c\u0435\u0441\u0442\u0438\u043c\u043e\u0441\u0442\u044c\r\n- **Python 3.11+** \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u0435\u043d \u0434\u043b\u044f \u0441\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0433\u043e async \u0441\u0438\u043d\u0442\u0430\u043a\u0441\u0438\u0441\u0430\r\n- **Pydantic v2** \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e\u0439 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438\r\n- **httpx** \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f \u0432\u0435\u0440\u0441\u0438\u044f 0.25.0+\r\n\r\n## \ud83e\udd1d \u0412\u043a\u043b\u0430\u0434 \u0432 \u0440\u0430\u0437\u0432\u0438\u0442\u0438\u0435\r\n\r\n### \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438\r\n\r\n```bash\r\n# \u041a\u043b\u043e\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435\r\ngit clone https://github.com/Rusik636/nalogo.git\r\ncd nalogo\r\n\r\n# \u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0432\u0438\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f\r\npython -m venv .venv\r\nsource .venv/bin/activate # Linux/Mac\r\n# \u0438\u043b\u0438\r\n.venv\\Scripts\\activate # Windows\r\n\r\n# \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438\r\npip install -e \".[dev]\"\r\n\r\n# \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 pre-commit \u0445\u0443\u043a\u043e\u0432\r\npre-commit install\r\n```\r\n\r\n### \u0417\u0430\u043f\u0443\u0441\u043a \u043f\u0440\u043e\u0432\u0435\u0440\u043e\u043a \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0430\r\n\r\n```bash\r\n# \u041b\u0438\u043d\u0442\u0438\u043d\u0433\r\nruff check .\r\n\r\n# \u0424\u043e\u0440\u043c\u0430\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435\r\nblack .\r\n\r\n# \u0422\u0438\u043f\u0438\u0437\u0430\u0446\u0438\u044f\r\nmypy nalogo/\r\n\r\n# \u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u044c\r\nbandit -r nalogo/\r\n\r\n# \u041f\u043e\u043b\u043d\u0430\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 (\u043a\u0430\u043a \u0432 CI)\r\npytest --cov=nalogo --cov-fail-under=80\r\n```\r\n\r\n### \u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 PR\r\n\r\n1. \u0421\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u0432\u0435\u0442\u043a\u0443 \u0434\u043b\u044f \u0444\u0438\u0447\u0438: `git checkout -b feature/amazing-feature`\r\n2. \u041d\u0430\u043f\u0438\u0448\u0438\u0442\u0435 \u0442\u0435\u0441\u0442\u044b \u0434\u043b\u044f \u043d\u043e\u0432\u043e\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u0438\r\n3. \u0423\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c \u0447\u0442\u043e \u0432\u0441\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043f\u0440\u043e\u0445\u043e\u0434\u044f\u0442\r\n4. \u0421\u043e\u0437\u0434\u0430\u0439\u0442\u0435 PR \u0441 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u044b\u043c \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435\u043c \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439\r\n\r\n## \ud83d\udcc4 \u041b\u0438\u0446\u0435\u043d\u0437\u0438\u044f\r\n\r\nMIT License - \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e\u0441\u0442\u0438 \u0432 \u0444\u0430\u0439\u043b\u0435 [LICENSE](LICENSE).\r\n\r\n## \ud83d\ude4f \u0411\u043b\u0430\u0433\u043e\u0434\u0430\u0440\u043d\u043e\u0441\u0442\u0438\r\n\r\n- **Artem Dubinin** ([shoman4eg](https://github.com/shoman4eg)) - \u0430\u0432\u0442\u043e\u0440 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b\u044c\u043d\u043e\u0439 PHP \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438\r\n- **\u041a\u043e\u043c\u0430\u043d\u0434\u0430 httpx** - \u0437\u0430 \u043e\u0442\u043b\u0438\u0447\u043d\u044b\u0439 async HTTP \u043a\u043b\u0438\u0435\u043d\u0442\r\n- **\u041a\u043e\u043c\u0430\u043d\u0434\u0430 Pydantic** - \u0437\u0430 \u043c\u043e\u0449\u043d\u0443\u044e \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044e \u0434\u0430\u043d\u043d\u044b\u0445\r\n- **\u0421\u043e\u043e\u0431\u0449\u0435\u0441\u0442\u0432\u043e Python** - \u0437\u0430 async/await \u0438 \u0441\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u044b\r\n\r\n## \ud83d\udcde \u041f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430\r\n\r\n- \ud83d\udccb **Issues**: [GitHub Issues](https://github.com/Rusik636/nalogo/issues)\r\n- \ud83d\udcd6 **\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044f**: [README.md](README.md)\r\n- \ud83d\udcac **\u041e\u0431\u0441\u0443\u0436\u0434\u0435\u043d\u0438\u044f**: [GitHub Discussions](https://github.com/Rusik636/nalogo/discussions)\r\n- \ud83d\udce7 **Email**: ruslan.prokshin@mail.ru\r\n\r\n---\r\n\r\n**\u0421\u0434\u0435\u043b\u0430\u043d\u043e \u0441 \u2764\ufe0f \u0434\u043b\u044f Python-\u0441\u043e\u043e\u0431\u0449\u0435\u0441\u0442\u0432\u0430**\r\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Asynchronous Python client for Russian self-employed tax service API (Moy Nalog / lknpd.nalog.ru)",
"version": "1.0.0",
"project_urls": {
"Bug Reports": "https://github.com/your-org/nalogo/issues",
"Changelog": "https://github.com/your-org/nalogo/blob/main/CHANGELOG.md",
"Documentation": "https://github.com/your-org/nalogo#readme",
"Homepage": "https://github.com/your-org/nalogo",
"Original PHP Library": "https://github.com/shoman4eg/moy-nalog",
"Repository": "https://github.com/your-org/nalogo"
},
"split_keywords": [
"nalog",
" tax",
" self-employed",
" api",
" async",
" russia",
" moy-nalog",
" freelancer",
" invoice",
" receipt",
" lknpd",
" \u0441\u0430\u043c\u043e\u0437\u0430\u043d\u044f\u0442\u044b\u0439"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "046debb0e0320437bce0c284b5802236fd96a412feb690b972a870624e1a880a",
"md5": "4269ddb5b1f260f6152481b4ebdea4cd",
"sha256": "4ca1d67521bb37fe9aee33102a6b79af5e2a155e7cb8f96765815b5d42946320"
},
"downloads": -1,
"filename": "nalogo-1.0.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "4269ddb5b1f260f6152481b4ebdea4cd",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.11",
"size": 30873,
"upload_time": "2025-08-15T21:07:06",
"upload_time_iso_8601": "2025-08-15T21:07:06.569857Z",
"url": "https://files.pythonhosted.org/packages/04/6d/ebb0e0320437bce0c284b5802236fd96a412feb690b972a870624e1a880a/nalogo-1.0.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "e784dbc5a41d6d01b9e3d786c95508aae1ece4a9fb9825232574d4b54c84e6dc",
"md5": "0fcbc909a7ad857e1ef110f33523dbe7",
"sha256": "e53edbf4b498928a1a840cf8f6adad28d47e1b98f5d95bf9f7723c796f0a3c60"
},
"downloads": -1,
"filename": "nalogo-1.0.0.tar.gz",
"has_sig": false,
"md5_digest": "0fcbc909a7ad857e1ef110f33523dbe7",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.11",
"size": 37332,
"upload_time": "2025-08-15T21:07:08",
"upload_time_iso_8601": "2025-08-15T21:07:08.288604Z",
"url": "https://files.pythonhosted.org/packages/e7/84/dbc5a41d6d01b9e3d786c95508aae1ece4a9fb9825232574d4b54c84e6dc/nalogo-1.0.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-08-15 21:07:08",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "your-org",
"github_project": "nalogo",
"github_not_found": true,
"lcname": "nalogo"
}