uapg


Nameuapg JSON
Version 0.1.0a3 PyPI version JSON
download
home_pageNone
SummaryOPC UA PostgreSQL History Storage Backend
upload_time2025-09-03 07:43:29
maintainerNone
docs_urlNone
authorNone
requires_python>=3.12
licenseNone
keywords opc-ua postgresql timescaledb history industrial
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # UAPG - OPC UA PostgreSQL History Storage Backend

[![Python 3.12](https://img.shields.io/badge/python-3.12-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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[![Python 3.12](https://img.shields.io/badge/python-3.12-blue.svg)](https://www.python.org/downloads/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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"
}
        
Elapsed time: 0.90379s