- Home »

Обработка исключения KeyError в Python — примеры
Если вы писали хотя бы минимальный Python-код для автоматизации серверных задач, то наверняка сталкивались с ошибкой KeyError. Это одна из самых частых ошибок, которая может случиться в самый неподходящий момент — когда ваш скрипт мониторинга падает на продакшене, или когда автоматизация развертывания виснет из-за отсутствующего ключа в конфигурации. Сегодня разберем, как корректно обрабатывать KeyError, чтобы ваши скрипты работали стабильно и предсказуемо.
KeyError — это не просто досадная ошибка, это часть жизни любого системного администратора или DevOps-инженера. Парсинг JSON-ответов от API, работа с конфигурационными файлами, обработка переменных окружения — везде может возникнуть ситуация, когда нужного ключа просто нет. Научимся обрабатывать такие ситуации элегантно и надежно.
Что такое KeyError и почему это происходит
KeyError возникает, когда вы пытаетесь получить доступ к ключу словаря, которого не существует. В контексте серверного администрирования это может произойти в десятках сценариев:
- Парсинг JSON-ответов от мониторинговых систем
- Работа с конфигурационными файлами
- Обработка переменных окружения
- Анализ логов в структурированном формате
- Работа с метаданными облачных провайдеров
Вот типичный пример, который может сломать ваш скрипт мониторинга:
import json
import requests
# Получаем статус сервера
response = requests.get('http://server-status.example.com/api/status')
data = json.loads(response.text)
# Опасно! Может вызвать KeyError
print(f"CPU usage: {data['cpu_usage']}%")
print(f"Memory usage: {data['memory_usage']}%")
print(f"Disk usage: {data['disk_usage']}%")
Если API изменится или вернет неполные данные, скрипт упадет с KeyError. Это особенно болезненно на продакшене, где стабильность критически важна.
Методы обработки KeyError: от простых к продвинутым
Есть несколько способов обработки KeyError, каждый со своими плюсами и минусами. Рассмотрим их в порядке от простых к более сложным:
1. Классический try/except
import json
import requests
def get_server_status():
try:
response = requests.get('http://server-status.example.com/api/status')
data = json.loads(response.text)
cpu_usage = data['cpu_usage']
memory_usage = data['memory_usage']
disk_usage = data['disk_usage']
return {
'cpu': cpu_usage,
'memory': memory_usage,
'disk': disk_usage
}
except KeyError as e:
print(f"Missing key in API response: {e}")
return None
except requests.RequestException as e:
print(f"Network error: {e}")
return None
2. Использование метода get() с дефолтными значениями
def get_server_status_safe():
try:
response = requests.get('http://server-status.example.com/api/status')
data = json.loads(response.text)
return {
'cpu': data.get('cpu_usage', 0),
'memory': data.get('memory_usage', 0),
'disk': data.get('disk_usage', 0),
'status': data.get('status', 'unknown')
}
except requests.RequestException as e:
print(f"Network error: {e}")
return None
3. Проверка наличия ключей с помощью оператора in
def validate_server_response(data):
required_keys = ['cpu_usage', 'memory_usage', 'disk_usage']
missing_keys = [key for key in required_keys if key not in data]
if missing_keys:
raise ValueError(f"Missing required keys: {missing_keys}")
return True
def get_server_status_validated():
try:
response = requests.get('http://server-status.example.com/api/status')
data = json.loads(response.text)
validate_server_response(data)
return {
'cpu': data['cpu_usage'],
'memory': data['memory_usage'],
'disk': data['disk_usage']
}
except ValueError as e:
print(f"Validation error: {e}")
return None
except requests.RequestException as e:
print(f"Network error: {e}")
return None
Практические примеры из реальной жизни
Давайте рассмотрим конкретные случаи, с которыми вы можете столкнуться при работе с серверами:
Пример 1: Мониторинг системных метрик
import psutil
import json
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def collect_system_metrics():
"""Собирает системные метрики с обработкой возможных ошибок"""
metrics = {}
try:
# CPU метрики
cpu_info = psutil.cpu_percent(interval=1, percpu=True)
metrics['cpu'] = {
'overall': psutil.cpu_percent(interval=1),
'per_core': cpu_info
}
# Memory метрики
memory = psutil.virtual_memory()
metrics['memory'] = {
'total': memory.total,
'available': memory.available,
'percent': memory.percent,
'used': memory.used
}
# Disk метрики
disk_usage = psutil.disk_usage('/')
metrics['disk'] = {
'total': disk_usage.total,
'used': disk_usage.used,
'free': disk_usage.free,
'percent': (disk_usage.used / disk_usage.total) * 100
}
# Network метрики
network = psutil.net_io_counters()
metrics['network'] = {
'bytes_sent': network.bytes_sent,
'bytes_recv': network.bytes_recv,
'packets_sent': network.packets_sent,
'packets_recv': network.packets_recv
}
return metrics
except Exception as e:
logger.error(f"Error collecting metrics: {e}")
return None
def process_metrics(metrics):
"""Обрабатывает метрики с защитой от KeyError"""
if not metrics:
return None
try:
# Безопасное извлечение данных
cpu_usage = metrics.get('cpu', {}).get('overall', 0)
memory_usage = metrics.get('memory', {}).get('percent', 0)
disk_usage = metrics.get('disk', {}).get('percent', 0)
# Проверяем критические значения
alerts = []
if cpu_usage > 80:
alerts.append(f"High CPU usage: {cpu_usage}%")
if memory_usage > 85:
alerts.append(f"High memory usage: {memory_usage}%")
if disk_usage > 90:
alerts.append(f"High disk usage: {disk_usage}%")
return {
'cpu_usage': cpu_usage,
'memory_usage': memory_usage,
'disk_usage': disk_usage,
'alerts': alerts,
'status': 'critical' if alerts else 'normal'
}
except Exception as e:
logger.error(f"Error processing metrics: {e}")
return None
# Использование
if __name__ == "__main__":
metrics = collect_system_metrics()
result = process_metrics(metrics)
if result:
print(json.dumps(result, indent=2))
else:
print("Failed to collect or process metrics")
Пример 2: Обработка конфигурационных файлов
import yaml
import json
import os
from typing import Dict, Any, Optional
class ConfigManager:
"""Менеджер конфигурации с безопасной обработкой ключей"""
def __init__(self, config_path: str):
self.config_path = config_path
self.config = self._load_config()
def _load_config(self) -> Dict[str, Any]:
"""Загружает конфигурацию из файла"""
try:
if not os.path.exists(self.config_path):
raise FileNotFoundError(f"Config file not found: {self.config_path}")
with open(self.config_path, 'r') as f:
if self.config_path.endswith('.yaml') or self.config_path.endswith('.yml'):
return yaml.safe_load(f)
elif self.config_path.endswith('.json'):
return json.load(f)
else:
raise ValueError("Unsupported config format")
except Exception as e:
print(f"Error loading config: {e}")
return {}
def get(self, key_path: str, default: Any = None) -> Any:
"""
Получает значение по пути ключа (например, 'database.host')
"""
try:
keys = key_path.split('.')
value = self.config
for key in keys:
if isinstance(value, dict) and key in value:
value = value[key]
else:
return default
return value
except Exception:
return default
def get_required(self, key_path: str) -> Any:
"""
Получает обязательное значение, вызывает исключение если ключ отсутствует
"""
value = self.get(key_path)
if value is None:
raise KeyError(f"Required config key not found: {key_path}")
return value
def validate_required_keys(self, required_keys: list) -> bool:
"""
Проверяет наличие всех обязательных ключей
"""
missing_keys = []
for key_path in required_keys:
if self.get(key_path) is None:
missing_keys.append(key_path)
if missing_keys:
raise ValueError(f"Missing required config keys: {missing_keys}")
return True
# Пример использования
def setup_database_connection():
"""Настройка подключения к базе данных с обработкой ошибок"""
config = ConfigManager('/etc/myapp/config.yaml')
# Проверяем обязательные ключи
required_keys = [
'database.host',
'database.port',
'database.name',
'database.user'
]
try:
config.validate_required_keys(required_keys)
# Получаем настройки
db_config = {
'host': config.get_required('database.host'),
'port': config.get('database.port', 5432),
'database': config.get_required('database.name'),
'user': config.get_required('database.user'),
'password': config.get('database.password', ''),
'ssl_mode': config.get('database.ssl_mode', 'require'),
'timeout': config.get('database.timeout', 30)
}
print("Database configuration loaded successfully")
return db_config
except (KeyError, ValueError) as e:
print(f"Configuration error: {e}")
return None
# Пример конфигурационного файла (config.yaml)
config_example = """
database:
host: localhost
port: 5432
name: myapp
user: myuser
password: mypassword
ssl_mode: require
timeout: 30
server:
host: 0.0.0.0
port: 8080
workers: 4
logging:
level: INFO
file: /var/log/myapp.log
"""
Сравнение методов обработки KeyError
Метод | Преимущества | Недостатки | Лучше использовать когда |
---|---|---|---|
try/except | Полный контроль над ошибкой, возможность логирования | Может маскировать другие ошибки | Нужна детальная обработка ошибок |
dict.get() | Простота, читаемость кода | Может скрыть логические ошибки | Есть разумные дефолтные значения |
Проверка ‘in’ | Явная проверка наличия ключа | Дополнительный код для каждого ключа | Нужна валидация структуры данных |
collections.defaultdict | Автоматическое создание дефолтных значений | Может создавать нежелательные ключи | Работа с счетчиками и группировкой |
Продвинутые техники с использованием дополнительных библиотек
Использование Pydantic для валидации данных
from pydantic import BaseModel, ValidationError
from typing import Optional
import json
class ServerStatus(BaseModel):
cpu_usage: float
memory_usage: float
disk_usage: float
status: str = "unknown"
uptime: Optional[int] = None
class Config:
# Разрешить дополнительные поля
extra = "allow"
def parse_server_status(json_data: str) -> Optional[ServerStatus]:
"""
Парсит JSON с валидацией через Pydantic
"""
try:
data = json.loads(json_data)
return ServerStatus(**data)
except ValidationError as e:
print(f"Validation error: {e}")
return None
except json.JSONDecodeError as e:
print(f"JSON decode error: {e}")
return None
# Пример использования
json_response = '{"cpu_usage": 45.2, "memory_usage": 67.8, "disk_usage": 23.1}'
server_status = parse_server_status(json_response)
if server_status:
print(f"CPU: {server_status.cpu_usage}%")
print(f"Memory: {server_status.memory_usage}%")
print(f"Disk: {server_status.disk_usage}%")
Использование JSONPath для сложных структур
from jsonpath_ng import jsonpath, parse
import json
def safe_jsonpath_extract(data, path_expression, default=None):
"""
Безопасное извлечение данных с помощью JSONPath
"""
try:
jsonpath_expr = parse(path_expression)
matches = jsonpath_expr.find(data)
if matches:
return matches[0].value
else:
return default
except Exception as e:
print(f"JSONPath error: {e}")
return default
# Пример сложной структуры данных
complex_data = {
"servers": [
{
"name": "web-01",
"metrics": {
"cpu": {"usage": 45.2},
"memory": {"usage": 67.8},
"disk": {"usage": 23.1}
}
},
{
"name": "web-02",
"metrics": {
"cpu": {"usage": 52.1},
"memory": {"usage": 71.3}
# Отсутствует disk
}
}
]
}
# Безопасное извлечение данных
for i, server in enumerate(complex_data.get("servers", [])):
name = safe_jsonpath_extract(server, "name", f"server-{i}")
cpu = safe_jsonpath_extract(server, "metrics.cpu.usage", 0)
memory = safe_jsonpath_extract(server, "metrics.memory.usage", 0)
disk = safe_jsonpath_extract(server, "metrics.disk.usage", 0)
print(f"{name}: CPU={cpu}%, Memory={memory}%, Disk={disk}%")
Автоматизация и интеграция в серверные скрипты
Для системных администраторов особенно важно, чтобы скрипты работали надежно без человеческого вмешательства. Вот пример комплексного решения для мониторинга серверов:
#!/usr/bin/env python3
import json
import logging
import time
import sys
from datetime import datetime
from typing import Dict, List, Optional
import requests
import psutil
# Настройка логирования
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('/var/log/server-monitor.log'),
logging.StreamHandler(sys.stdout)
]
)
logger = logging.getLogger(__name__)
class ServerMonitor:
"""Класс для мониторинга серверов с устойчивой обработкой ошибок"""
def __init__(self, config_path: str = '/etc/server-monitor/config.json'):
self.config = self._load_config(config_path)
self.alerts = []
def _load_config(self, config_path: str) -> Dict:
"""Загружает конфигурацию с дефолтными значениями"""
default_config = {
'thresholds': {
'cpu': 80,
'memory': 85,
'disk': 90
},
'check_interval': 60,
'webhook_url': None,
'email_alerts': False
}
try:
with open(config_path, 'r') as f:
user_config = json.load(f)
default_config.update(user_config)
return default_config
except (FileNotFoundError, json.JSONDecodeError) as e:
logger.warning(f"Config file error: {e}. Using defaults.")
return default_config
def collect_metrics(self) -> Optional[Dict]:
"""Собирает системные метрики с обработкой ошибок"""
metrics = {
'timestamp': datetime.now().isoformat(),
'hostname': self.config.get('hostname', 'unknown')
}
try:
# CPU метрики
cpu_percent = psutil.cpu_percent(interval=1)
metrics['cpu'] = {
'usage': cpu_percent,
'count': psutil.cpu_count(),
'load_avg': psutil.getloadavg() if hasattr(psutil, 'getloadavg') else None
}
# Memory метрики
memory = psutil.virtual_memory()
metrics['memory'] = {
'usage': memory.percent,
'total': memory.total,
'available': memory.available,
'used': memory.used
}
# Disk метрики
disk_usage = psutil.disk_usage('/')
metrics['disk'] = {
'usage': (disk_usage.used / disk_usage.total) * 100,
'total': disk_usage.total,
'used': disk_usage.used,
'free': disk_usage.free
}
# Network метрики
network = psutil.net_io_counters()
metrics['network'] = {
'bytes_sent': network.bytes_sent,
'bytes_recv': network.bytes_recv,
'packets_sent': network.packets_sent,
'packets_recv': network.packets_recv
}
return metrics
except Exception as e:
logger.error(f"Error collecting metrics: {e}")
return None
def check_thresholds(self, metrics: Dict) -> List[str]:
"""Проверяет превышение пороговых значений"""
alerts = []
thresholds = self.config.get('thresholds', {})
try:
# Проверяем CPU
cpu_usage = metrics.get('cpu', {}).get('usage', 0)
cpu_threshold = thresholds.get('cpu', 80)
if cpu_usage > cpu_threshold:
alerts.append(f"High CPU usage: {cpu_usage:.1f}% (threshold: {cpu_threshold}%)")
# Проверяем Memory
memory_usage = metrics.get('memory', {}).get('usage', 0)
memory_threshold = thresholds.get('memory', 85)
if memory_usage > memory_threshold:
alerts.append(f"High memory usage: {memory_usage:.1f}% (threshold: {memory_threshold}%)")
# Проверяем Disk
disk_usage = metrics.get('disk', {}).get('usage', 0)
disk_threshold = thresholds.get('disk', 90)
if disk_usage > disk_threshold:
alerts.append(f"High disk usage: {disk_usage:.1f}% (threshold: {disk_threshold}%)")
except Exception as e:
logger.error(f"Error checking thresholds: {e}")
return alerts
def send_webhook_alert(self, alerts: List[str], metrics: Dict):
"""Отправляет уведомление через webhook"""
webhook_url = self.config.get('webhook_url')
if not webhook_url:
return
try:
payload = {
'hostname': metrics.get('hostname', 'unknown'),
'timestamp': metrics.get('timestamp'),
'alerts': alerts,
'metrics': {
'cpu': metrics.get('cpu', {}).get('usage', 0),
'memory': metrics.get('memory', {}).get('usage', 0),
'disk': metrics.get('disk', {}).get('usage', 0)
}
}
response = requests.post(
webhook_url,
json=payload,
timeout=10
)
if response.status_code == 200:
logger.info("Webhook alert sent successfully")
else:
logger.error(f"Webhook failed with status: {response.status_code}")
except Exception as e:
logger.error(f"Error sending webhook: {e}")
def run_check(self):
"""Выполняет один цикл проверки"""
metrics = self.collect_metrics()
if not metrics:
logger.error("Failed to collect metrics")
return
alerts = self.check_thresholds(metrics)
if alerts:
logger.warning(f"Alerts detected: {alerts}")
self.send_webhook_alert(alerts, metrics)
else:
logger.info("All metrics within normal ranges")
# Логируем основные метрики
cpu = metrics.get('cpu', {}).get('usage', 0)
memory = metrics.get('memory', {}).get('usage', 0)
disk = metrics.get('disk', {}).get('usage', 0)
logger.info(f"CPU: {cpu:.1f}%, Memory: {memory:.1f}%, Disk: {disk:.1f}%")
def run_continuous(self):
"""Запускает непрерывный мониторинг"""
interval = self.config.get('check_interval', 60)
logger.info(f"Starting continuous monitoring (interval: {interval}s)")
while True:
try:
self.run_check()
time.sleep(interval)
except KeyboardInterrupt:
logger.info("Monitoring stopped by user")
break
except Exception as e:
logger.error(f"Unexpected error: {e}")
time.sleep(interval)
def main():
"""Основная функция"""
monitor = ServerMonitor()
if len(sys.argv) > 1 and sys.argv[1] == '--once':
monitor.run_check()
else:
monitor.run_continuous()
if __name__ == "__main__":
main()
Интеграция с системой мониторинга
Для работы на серверах нужно учитывать интеграцию с существующими системами мониторинга. Вот пример интеграции с Prometheus и Grafana:
from prometheus_client import start_http_server, Gauge, Counter
import time
import threading
class PrometheusMetrics:
"""Класс для экспорта метрик в Prometheus"""
def __init__(self):
# Метрики для Prometheus
self.cpu_usage = Gauge('server_cpu_usage_percent', 'CPU usage percentage')
self.memory_usage = Gauge('server_memory_usage_percent', 'Memory usage percentage')
self.disk_usage = Gauge('server_disk_usage_percent', 'Disk usage percentage')
self.errors_total = Counter('server_monitor_errors_total', 'Total monitoring errors')
def update_metrics(self, metrics: Dict):
"""Обновляет метрики для Prometheus"""
try:
cpu = metrics.get('cpu', {}).get('usage', 0)
memory = metrics.get('memory', {}).get('usage', 0)
disk = metrics.get('disk', {}).get('usage', 0)
self.cpu_usage.set(cpu)
self.memory_usage.set(memory)
self.disk_usage.set(disk)
except Exception as e:
self.errors_total.inc()
logger.error(f"Error updating Prometheus metrics: {e}")
class EnhancedServerMonitor(ServerMonitor):
"""Расширенный монитор с поддержкой Prometheus"""
def __init__(self, config_path: str = '/etc/server-monitor/config.json'):
super().__init__(config_path)
self.prometheus_metrics = PrometheusMetrics()
self.start_prometheus_server()
def start_prometheus_server(self):
"""Запускает HTTP сервер для Prometheus"""
port = self.config.get('prometheus_port', 8000)
try:
start_http_server(port)
logger.info(f"Prometheus metrics server started on port {port}")
except Exception as e:
logger.error(f"Failed to start Prometheus server: {e}")
def run_check(self):
"""Переопределенный метод с поддержкой Prometheus"""
super().run_check()
# Обновляем метрики для Prometheus
metrics = self.collect_metrics()
if metrics:
self.prometheus_metrics.update_metrics(metrics)
Полезные советы и лучшие практики
- Всегда используйте дефолтные значения: При работе с API или конфигурационными файлами всегда предусматривайте разумные дефолтные значения
- Логируйте все ошибки: KeyError может указывать на изменения в API или структуре данных
- Используйте схемы валидации: Pydantic, jsonschema или аналогичные библиотеки помогут выявить проблемы на раннем этапе
- Тестируйте граничные случаи: Создавайте тесты с неполными или неверными данными
- Мониторьте частоту ошибок: Частые KeyError могут указывать на проблемы с источниками данных
Интересные факты и нестандартные применения
KeyError может быть не только проблемой, но и полезным инструментом:
1. Использование KeyError для реализации кэша
class SimpleCache:
def __init__(self):
self._cache = {}
def get(self, key, factory_func):
"""Получает значение или создает его через factory_func"""
try:
return self._cache[key]
except KeyError:
value = factory_func()
self._cache[key] = value
return value
# Использование
cache = SimpleCache()
def expensive_operation():
# Имитация дорогой операции
time.sleep(1)
return "expensive result"
# Первый вызов - выполнится factory_func
result1 = cache.get("key1", expensive_operation)
# Второй вызов - вернется из кэша
result2 = cache.get("key1", expensive_operation)
2. Использование в качестве сигнала для ленивой инициализации
class LazyServerManager:
def __init__(self):
self._connections = {}
def get_connection(self, server_name):
"""Создает подключение к серверу по требованию"""
try:
return self._connections[server_name]
except KeyError:
# Создаем подключение только когда оно нужно
connection = self._create_connection(server_name)
self._connections[server_name] = connection
return connection
def _create_connection(self, server_name):
# Логика создания подключения
return f"Connection to {server_name}"
Производительность и оптимизация
Различные методы обработки KeyError имеют разную производительность. Вот бенчмарк для сравнения:
import time
from collections import defaultdict
def benchmark_methods(data, key, iterations=1000000):
"""Сравнивает производительность разных методов"""
# Метод 1: try/except
start = time.time()
for _ in range(iterations):
try:
value = data[key]
except KeyError:
value = None
try_except_time = time.time() - start
# Метод 2: dict.get()
start = time.time()
for _ in range(iterations):
value = data.get(key)
get_time = time.time() - start
# Метод 3: проверка 'in'
start = time.time()
for _ in range(iterations):
if key in data:
value = data[key]
else:
value = None
in_check_time = time.time() - start
print(f"try/except: {try_except_time:.4f}s")
print(f"dict.get(): {get_time:.4f}s")
print(f"'in' check: {in_check_time:.4f}s")
# Тестирование
test_data = {'existing_key': 'value'}
benchmark_methods(test_data, 'missing_key')
Развертывание скриптов на серверах
Для развертывания скриптов мониторинга на серверах рекомендую использовать качественные VPS или выделенные серверы с достаточными ресурсами для стабильной работы.
Пример systemd сервиса для автоматического запуска:
# /etc/systemd/system/server-monitor.service
[Unit]
Description=Server Monitor Service
After=network.target
[Service]
Type=simple
User=monitor
Group=monitor
WorkingDirectory=/opt/server-monitor
ExecStart=/usr/bin/python3 /opt/server-monitor/monitor.py
Restart=always
RestartSec=10
Environment=PYTHONPATH=/opt/server-monitor
[Install]
WantedBy=multi-user.target
Заключение и рекомендации
Правильная обработка KeyError — это не просто техническая деталь, а важная часть создания надежных серверных приложений. В зависимости от ситуации используйте разные подходы:
- Для API и внешних данных: Используйте try/except с подробным логированием
- Для конфигурационных файлов: Применяйте валидацию схем и дефолтные значения
- Для производительно критичных участков: Используйте dict.get() или проверку ‘in’
- Для сложных структур данных: Рассмотрите специализированные библиотеки типа Pydantic
Помните, что KeyError часто указывает на проблемы в архитектуре или изменения в источниках данных. Не просто подавляйте ошибки, а анализируйте их причины и принимайте соответствующие меры.
Качественная обработка исключений поможет создать стабильные и предсказуемые системы мониторинга, автоматизации и управления серверами. Это особенно важно в продакшн-среде, где любая необработанная ошибка может привести к серьезным проблемам.
В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.
Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.