# UAPG - OPC UA PostgreSQL History Storage Backend
[](https://www.python.org/downloads/)
[](https://opensource.org/licenses/MIT)
UAPG - это модуль для хранения исторических данных OPC UA в PostgreSQL с поддержкой TimescaleDB для эффективной работы с временными рядами.
## Возможности
- 📊 Хранение исторических данных OPC UA в PostgreSQL
- ⚡ Поддержка TimescaleDB для оптимизации временных рядов
- 🔄 Автоматическое управление жизненным циклом данных
- 📈 Индексация для быстрых запросов
- 🎯 Поддержка событий и изменений данных
- 🛡️ Валидация имен таблиц для безопасности
- 🚀 **Пул подключений PostgreSQL для высокой производительности**
- 🔒 **Решение проблемы "another operation is in progress"**
## Установка
```bash
pip install uapg
```
## Быстрый старт
### Базовое использование
```python
import asyncio
from uapg import HistoryPgSQL
async def main():
# Создание экземпляра истории с пулом подключений
history = HistoryPgSQL(
user='postgres',
password='your_password',
database='opcua',
host='localhost',
port=5432,
min_size=5, # Минимальное количество соединений в пуле
max_size=20 # Максимальное количество соединений в пуле
)
# Инициализация пула подключений
await history.init()
# Настройка историзации узла
await history.new_historized_node(
node_id=ua.NodeId(1, "MyVariable"),
period=timedelta(days=30), # Хранить данные 30 дней
count=10000 # Максимум 10000 записей
)
# Сохранение значения
datavalue = ua.DataValue(
Value=ua.Variant(42.0, ua.VariantType.Double),
SourceTimestamp=datetime.now(timezone.utc),
ServerTimestamp=datetime.now(timezone.utc)
)
await history.save_node_value(node_id, datavalue)
# Чтение истории
start_time = datetime.now(timezone.utc) - timedelta(hours=1)
end_time = datetime.now(timezone.utc)
results, continuation = await history.read_node_history(
node_id, start_time, end_time, nb_values=100
)
# Закрытие пула подключений
await history.stop()
# Запуск
asyncio.run(main())
```
### Конфигурация пула подключений
UAPG поддерживает настройку пула подключений для различных сценариев использования:
#### Для высоконагруженных систем
```python
history = HistoryPgSQL(
user='postgres',
password='your_password',
database='opcua',
host='localhost',
min_size=10, # Больше минимальных соединений для быстрого отклика
max_size=50 # Больше максимальных соединений для пиковых нагрузок
)
```
#### Для ресурсоэффективных систем
```python
history = HistoryPgSQL(
user='postgres',
password='your_password',
database='opcua',
host='localhost',
min_size=2, # Минимальное количество соединений
max_size=10 # Ограниченное количество максимальных соединений
)
```
#### Для сбалансированных систем (по умолчанию)
```python
history = HistoryPgSQL(
user='postgres',
password='your_password',
database='opcua',
host='localhost',
min_size=5, # Умеренное количество минимальных соединений
max_size=20 # Умеренное количество максимальных соединений
)
```
## Решение проблем
### Ошибка "another operation is in progress"
Эта ошибка возникает при использовании одного соединения для нескольких одновременных операций. UAPG решает эту проблему с помощью пула подключений:
- **До**: Одно соединение `asyncpg.Connection` для всех операций
- **После**: Пул соединений `asyncpg.Pool` с автоматическим управлением
```python
# Старый способ (может вызывать ошибки)
self._db = await asyncpg.connect(**self._conn_params)
await self._db.execute(query)
# Новый способ (решает проблему)
self._pool = await asyncpg.create_pool(**self._conn_params, min_size=5, max_size=20)
async with self._pool.acquire() as conn:
await conn.execute(query)
```
### Мониторинг пула подключений
```python
# Получение статуса пула
pool_status = await history._pool.get_status()
print(f"Active connections: {pool_status['active_connections']}")
print(f"Free connections: {pool_status['free_size']}")
```
## Требования
- Python 3.12+
- PostgreSQL 12+
- TimescaleDB (рекомендуется для больших объемов данных)
## Зависимости
- `asyncua>=1.0.0` - OPC UA клиент/сервер
- `asyncpg>=0.29.0` - Асинхронный драйвер PostgreSQL
## Примеры
### Базовый пример
```bash
cd examples
python basic_usage.py
```
### Продвинутая конфигурация пула
```bash
cd examples
python advanced_pool_config.py
```
## Разработка
### Установка для разработки
```bash
git clone https://github.com/rts-iot/uapg.git
cd uapg
pip install -e ".[dev]"
```
### Запуск тестов
```bash
pytest
```
### Форматирование кода
```bash
black src/
isort src/
```
### Проверка типов
```bash
mypy src/
```
## Лицензия
MIT License - см. файл [LICENSE](LICENSE) для подробностей.
## Поддержка
Если у вас есть вопросы или проблемы, создайте issue в репозитории проекта.
Raw data
{
"_id": null,
"home_page": null,
"name": "uapg",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.12",
"maintainer_email": null,
"keywords": "opc-ua, postgresql, timescaledb, history, industrial",
"author": null,
"author_email": "CyberNet-Git <v.v.panfilov@gmail.com>",
"download_url": "https://files.pythonhosted.org/packages/43/9e/66e240e949cb89b4f2c1b8482f9ea5fda2fbc1eb5abe76b51261c5bd1086/uapg-0.1.0a3.tar.gz",
"platform": null,
"description": "# UAPG - OPC UA PostgreSQL History Storage Backend\n\n[](https://www.python.org/downloads/)\n[](https://opensource.org/licenses/MIT)\n\nUAPG - \u044d\u0442\u043e \u043c\u043e\u0434\u0443\u043b\u044c \u0434\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0438\u0441\u0442\u043e\u0440\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0434\u0430\u043d\u043d\u044b\u0445 OPC UA \u0432 PostgreSQL \u0441 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u043e\u0439 TimescaleDB \u0434\u043b\u044f \u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u043e\u0439 \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u043c\u0438 \u0440\u044f\u0434\u0430\u043c\u0438.\n\n## \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438\n\n- \ud83d\udcca \u0425\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0438\u0441\u0442\u043e\u0440\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0434\u0430\u043d\u043d\u044b\u0445 OPC UA \u0432 PostgreSQL\n- \u26a1 \u041f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430 TimescaleDB \u0434\u043b\u044f \u043e\u043f\u0442\u0438\u043c\u0438\u0437\u0430\u0446\u0438\u0438 \u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u0440\u044f\u0434\u043e\u0432\n- \ud83d\udd04 \u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0436\u0438\u0437\u043d\u0435\u043d\u043d\u044b\u043c \u0446\u0438\u043a\u043b\u043e\u043c \u0434\u0430\u043d\u043d\u044b\u0445\n- \ud83d\udcc8 \u0418\u043d\u0434\u0435\u043a\u0441\u0430\u0446\u0438\u044f \u0434\u043b\u044f \u0431\u044b\u0441\u0442\u0440\u044b\u0445 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432\n- \ud83c\udfaf \u041f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0438 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439 \u0434\u0430\u043d\u043d\u044b\u0445\n- \ud83d\udee1\ufe0f \u0412\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f \u0438\u043c\u0435\u043d \u0442\u0430\u0431\u043b\u0438\u0446 \u0434\u043b\u044f \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u0438\n- \ud83d\ude80 **\u041f\u0443\u043b \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439 PostgreSQL \u0434\u043b\u044f \u0432\u044b\u0441\u043e\u043a\u043e\u0439 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438**\n- \ud83d\udd12 **\u0420\u0435\u0448\u0435\u043d\u0438\u0435 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \"another operation is in progress\"**\n\n## \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430\n\n```bash\npip install uapg\n```\n\n## \u0411\u044b\u0441\u0442\u0440\u044b\u0439 \u0441\u0442\u0430\u0440\u0442\n\n### \u0411\u0430\u0437\u043e\u0432\u043e\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\n\n```python\nimport asyncio\nfrom uapg import HistoryPgSQL\n\nasync def main():\n # \u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u0430 \u0438\u0441\u0442\u043e\u0440\u0438\u0438 \u0441 \u043f\u0443\u043b\u043e\u043c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439\n history = HistoryPgSQL(\n user='postgres',\n password='your_password',\n database='opcua',\n host='localhost',\n port=5432,\n min_size=5, # \u041c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0439 \u0432 \u043f\u0443\u043b\u0435\n max_size=20 # \u041c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0439 \u0432 \u043f\u0443\u043b\u0435\n )\n \n # \u0418\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u043f\u0443\u043b\u0430 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439\n await history.init()\n \n # \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0438\u0441\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0443\u0437\u043b\u0430\n await history.new_historized_node(\n node_id=ua.NodeId(1, \"MyVariable\"),\n period=timedelta(days=30), # \u0425\u0440\u0430\u043d\u0438\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 30 \u0434\u043d\u0435\u0439\n count=10000 # \u041c\u0430\u043a\u0441\u0438\u043c\u0443\u043c 10000 \u0437\u0430\u043f\u0438\u0441\u0435\u0439\n )\n \n # \u0421\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f\n datavalue = ua.DataValue(\n Value=ua.Variant(42.0, ua.VariantType.Double),\n SourceTimestamp=datetime.now(timezone.utc),\n ServerTimestamp=datetime.now(timezone.utc)\n )\n await history.save_node_value(node_id, datavalue)\n \n # \u0427\u0442\u0435\u043d\u0438\u0435 \u0438\u0441\u0442\u043e\u0440\u0438\u0438\n start_time = datetime.now(timezone.utc) - timedelta(hours=1)\n end_time = datetime.now(timezone.utc)\n results, continuation = await history.read_node_history(\n node_id, start_time, end_time, nb_values=100\n )\n \n # \u0417\u0430\u043a\u0440\u044b\u0442\u0438\u0435 \u043f\u0443\u043b\u0430 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439\n await history.stop()\n\n# \u0417\u0430\u043f\u0443\u0441\u043a\nasyncio.run(main())\n```\n\n### \u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u043f\u0443\u043b\u0430 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439\n\nUAPG \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443 \u043f\u0443\u043b\u0430 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439 \u0434\u043b\u044f \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0445 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0435\u0432 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f:\n\n#### \u0414\u043b\u044f \u0432\u044b\u0441\u043e\u043a\u043e\u043d\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043d\u044b\u0445 \u0441\u0438\u0441\u0442\u0435\u043c\n```python\nhistory = HistoryPgSQL(\n user='postgres',\n password='your_password',\n database='opcua',\n host='localhost',\n min_size=10, # \u0411\u043e\u043b\u044c\u0448\u0435 \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0445 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0439 \u0434\u043b\u044f \u0431\u044b\u0441\u0442\u0440\u043e\u0433\u043e \u043e\u0442\u043a\u043b\u0438\u043a\u0430\n max_size=50 # \u0411\u043e\u043b\u044c\u0448\u0435 \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0445 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0439 \u0434\u043b\u044f \u043f\u0438\u043a\u043e\u0432\u044b\u0445 \u043d\u0430\u0433\u0440\u0443\u0437\u043e\u043a\n)\n```\n\n#### \u0414\u043b\u044f \u0440\u0435\u0441\u0443\u0440\u0441\u043e\u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u044b\u0445 \u0441\u0438\u0441\u0442\u0435\u043c\n```python\nhistory = HistoryPgSQL(\n user='postgres',\n password='your_password',\n database='opcua',\n host='localhost',\n min_size=2, # \u041c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0439\n max_size=10 # \u041e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u043d\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0445 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0439\n)\n```\n\n#### \u0414\u043b\u044f \u0441\u0431\u0430\u043b\u0430\u043d\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u0441\u0438\u0441\u0442\u0435\u043c (\u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e)\n```python\nhistory = HistoryPgSQL(\n user='postgres',\n password='your_password',\n database='opcua',\n host='localhost',\n min_size=5, # \u0423\u043c\u0435\u0440\u0435\u043d\u043d\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0445 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0439\n max_size=20 # \u0423\u043c\u0435\u0440\u0435\u043d\u043d\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0445 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0439\n)\n```\n\n## \u0420\u0435\u0448\u0435\u043d\u0438\u0435 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\n\n### \u041e\u0448\u0438\u0431\u043a\u0430 \"another operation is in progress\"\n\n\u042d\u0442\u0430 \u043e\u0448\u0438\u0431\u043a\u0430 \u0432\u043e\u0437\u043d\u0438\u043a\u0430\u0435\u0442 \u043f\u0440\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438 \u043e\u0434\u043d\u043e\u0433\u043e \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0439. UAPG \u0440\u0435\u0448\u0430\u0435\u0442 \u044d\u0442\u0443 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0443 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043f\u0443\u043b\u0430 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439:\n\n- **\u0414\u043e**: \u041e\u0434\u043d\u043e \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 `asyncpg.Connection` \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0439\n- **\u041f\u043e\u0441\u043b\u0435**: \u041f\u0443\u043b \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0439 `asyncpg.Pool` \u0441 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u043c \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u043c\n\n```python\n# \u0421\u0442\u0430\u0440\u044b\u0439 \u0441\u043f\u043e\u0441\u043e\u0431 (\u043c\u043e\u0436\u0435\u0442 \u0432\u044b\u0437\u044b\u0432\u0430\u0442\u044c \u043e\u0448\u0438\u0431\u043a\u0438)\nself._db = await asyncpg.connect(**self._conn_params)\nawait self._db.execute(query)\n\n# \u041d\u043e\u0432\u044b\u0439 \u0441\u043f\u043e\u0441\u043e\u0431 (\u0440\u0435\u0448\u0430\u0435\u0442 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0443)\nself._pool = await asyncpg.create_pool(**self._conn_params, min_size=5, max_size=20)\nasync with self._pool.acquire() as conn:\n await conn.execute(query)\n```\n\n### \u041c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433 \u043f\u0443\u043b\u0430 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439\n\n```python\n# \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0441\u0442\u0430\u0442\u0443\u0441\u0430 \u043f\u0443\u043b\u0430\npool_status = await history._pool.get_status()\nprint(f\"Active connections: {pool_status['active_connections']}\")\nprint(f\"Free connections: {pool_status['free_size']}\")\n```\n\n## \u0422\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u044f\n\n- Python 3.12+\n- PostgreSQL 12+\n- TimescaleDB (\u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u0431\u043e\u043b\u044c\u0448\u0438\u0445 \u043e\u0431\u044a\u0435\u043c\u043e\u0432 \u0434\u0430\u043d\u043d\u044b\u0445)\n\n## \u0417\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438\n\n- `asyncua>=1.0.0` - OPC UA \u043a\u043b\u0438\u0435\u043d\u0442/\u0441\u0435\u0440\u0432\u0435\u0440\n- `asyncpg>=0.29.0` - \u0410\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439 \u0434\u0440\u0430\u0439\u0432\u0435\u0440 PostgreSQL\n\n## \u041f\u0440\u0438\u043c\u0435\u0440\u044b\n\n### \u0411\u0430\u0437\u043e\u0432\u044b\u0439 \u043f\u0440\u0438\u043c\u0435\u0440\n```bash\ncd examples\npython basic_usage.py\n```\n\n### \u041f\u0440\u043e\u0434\u0432\u0438\u043d\u0443\u0442\u0430\u044f \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u043f\u0443\u043b\u0430\n```bash\ncd examples\npython advanced_pool_config.py\n```\n\n## \u0420\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0430\n\n### \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0434\u043b\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438\n\n```bash\ngit clone https://github.com/rts-iot/uapg.git\ncd uapg\npip install -e \".[dev]\"\n```\n\n### \u0417\u0430\u043f\u0443\u0441\u043a \u0442\u0435\u0441\u0442\u043e\u0432\n\n```bash\npytest\n```\n\n### \u0424\u043e\u0440\u043c\u0430\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043a\u043e\u0434\u0430\n\n```bash\nblack src/\nisort src/\n```\n\n### \u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0442\u0438\u043f\u043e\u0432\n\n```bash\nmypy src/\n```\n\n## \u041b\u0438\u0446\u0435\u043d\u0437\u0438\u044f\n\nMIT License - \u0441\u043c. \u0444\u0430\u0439\u043b [LICENSE](LICENSE) \u0434\u043b\u044f \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e\u0441\u0442\u0435\u0439.\n\n## \u041f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430\n\n\u0415\u0441\u043b\u0438 \u0443 \u0432\u0430\u0441 \u0435\u0441\u0442\u044c \u0432\u043e\u043f\u0440\u043e\u0441\u044b \u0438\u043b\u0438 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b, \u0441\u043e\u0437\u0434\u0430\u0439\u0442\u0435 issue \u0432 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438 \u043f\u0440\u043e\u0435\u043a\u0442\u0430.\n",
"bugtrack_url": null,
"license": null,
"summary": "OPC UA PostgreSQL History Storage Backend",
"version": "0.1.0a3",
"project_urls": {
"Documentation": "https://github.com/CyberNet-Git/uapg#readme",
"Homepage": "https://github.com/CyberNet-Git/uapg",
"Issues": "https://github.com/CyberNet-Git/uapg/issues",
"Repository": "https://github.com/CyberNet-Git/uapg"
},
"split_keywords": [
"opc-ua",
" postgresql",
" timescaledb",
" history",
" industrial"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "9e8d831ee712112134e4d0778df8c6b6f2f812304dc20f4b3ad216229f66b4a4",
"md5": "f38528b35d59c644af06841b334d186c",
"sha256": "a1ba6e32641fc4353cf98e21c7b81e292d057bd535b5b253e1d77b76c75d48a7"
},
"downloads": -1,
"filename": "uapg-0.1.0a3-py3-none-any.whl",
"has_sig": false,
"md5_digest": "f38528b35d59c644af06841b334d186c",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.12",
"size": 44819,
"upload_time": "2025-09-03T07:43:28",
"upload_time_iso_8601": "2025-09-03T07:43:28.026469Z",
"url": "https://files.pythonhosted.org/packages/9e/8d/831ee712112134e4d0778df8c6b6f2f812304dc20f4b3ad216229f66b4a4/uapg-0.1.0a3-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "439e66e240e949cb89b4f2c1b8482f9ea5fda2fbc1eb5abe76b51261c5bd1086",
"md5": "49e22f9f0e4d663a70fc8ec07e2a0d2f",
"sha256": "a0ccf94b0c9256b1a437f7ba8b295c7f93bea1b9520d429697a4d411bf4613dd"
},
"downloads": -1,
"filename": "uapg-0.1.0a3.tar.gz",
"has_sig": false,
"md5_digest": "49e22f9f0e4d663a70fc8ec07e2a0d2f",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.12",
"size": 49586,
"upload_time": "2025-09-03T07:43:29",
"upload_time_iso_8601": "2025-09-03T07:43:29.432677Z",
"url": "https://files.pythonhosted.org/packages/43/9e/66e240e949cb89b4f2c1b8482f9ea5fda2fbc1eb5abe76b51261c5bd1086/uapg-0.1.0a3.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-09-03 07:43:29",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "CyberNet-Git",
"github_project": "uapg#readme",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "uapg"
}