Metadata-Version: 2.5
Name: cdc-1c
Version: 0.1.9
Summary: Change data capture (CDC) from 1C:Enterprise to your data warehouse
Project-URL: Homepage, https://github.com/pavel-v-sobolev/cdc_1C
Project-URL: Repository, https://github.com/pavel-v-sobolev/cdc_1C
Project-URL: Issues, https://github.com/pavel-v-sobolev/cdc_1C/issues
Author-email: Pavel Sobolev <pavel-v-sobolev@yandex.ru>
License-Expression: MIT
License-File: LICENSE
Keywords: 1c,1c-enterprise,cdc,data-engineering,dwh,etl,postgres,python
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Information Technology
Classifier: Intended Audience :: Science/Research
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Database
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Requires-Dist: dbmerge>=1.0.20
Requires-Dist: requests>=2.33.0
Requires-Dist: sqlalchemy>=2.0.49
Requires-Dist: xmltodict>=1.0.4
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == 'dev'
Provides-Extra: postgres
Requires-Dist: psycopg2-binary>=2.9.12; extra == 'postgres'
Description-Content-Type: text/markdown

[![PyPI version](https://img.shields.io/pypi/v/cdc-1c.svg)](https://pypi.org/project/cdc-1c/)
[![Python versions](https://img.shields.io/pypi/pyversions/cdc-1c.svg)](https://pypi.org/project/cdc-1c/)


**cdc-1c** is a docker container and a Python library, that provides 1C system data loading to data warehouse using Change Data Capture apporach. \
It engages standard ODATA mechanism and standard 1C exchange plan mechanism to extract data from 1C system and upsert changes to the target DB.

**cdc-1c** - это докер контейнер и python-библиотека, предназначенные для получения данных из 1С, использующий подход CDC (загрузка изменений данных). \
Продукт использует стандартный интерфейс ODATA и механизм планов обмена для выгрузки изменений данных из системы 1С и обновления данных в целевой БД.

# Что нужно для работы
1) опубликовать базу 1с на web
2) создать пользователя odata и дать ему необходимые права
3) настроить план обмена в конфигураторе и включить в его состав нужные объекты 1с
3) настроить узел обмена с использованием внешней обработки `cdc-1c.odt`
4) поднять docker контейнер (плока не доступен - todo) для получения данных или адаптировать под свой вариант, исспользуя библиотеку python cdc-1c

# Использование библиотеки python

Библиотека даёт оркестратор `Replicator1C`, который читает изменения из 1С (OData + план обмена) и
складывает их в целевую БД, подтверждая приём пакета только после успешного сохранения. БД
передаётся готовым SQLAlchemy `engine`.

## Установка

```bash
pip install cdc-1c

# c драйвером PostgreSQL (тестовая СУБД):
pip install "cdc-1c[postgres]"
```

Поддерживается Python 3.10+. Модуль тестировался на PostgreSQL, но целевой может быть любая БД из
числа поддерживаемых модулем `dbmerge` (через него идёт запись). Сама библиотека драйвер БД не
импортирует — его ставите под свою СУБД (для PostgreSQL — extra `[postgres]` выше).

## Быстрый старт

```python
from sqlalchemy import create_engine
from cdc_1c import Replicator1C

engine = create_engine("postgresql+psycopg2://user:pass@localhost:5432/dwh", pool_size=5)

rep = Replicator1C(
    odata_url="http://host/base/odata/standard.odata",
    odata_auth=("odata", "secret"),        # (user, password) либо None без авторизации
    exchange_name="ДляODATA",              # имя плана обмена в 1С
    queue_guid="a9bc23c5-3689-11f1-926c-0800270bc6cb",  # Ref_Key узла обмена (очереди)
    engine=engine,
    db_schema="cdc_1c",                    # None → схема БД по умолчанию (public у Postgres)
    request_timeout=60,                    # таймаут HTTP-запросов к 1С, сек (по умолчанию 60 на коннект, 900 на ответ)
    full_load_workers=2,                   # число фоновых потоков полной выгрузки
)

rep.run_forever(interval=60)               # цикл опроса раз в 60 секунд
```


## Режимы: `run_once` и `run_forever`

```python
rep.run_once()                 # один цикл: read → save → notify (подтверждение только после save)
rep.run_forever(interval=60)   # бесконечный цикл run_once с паузой; фоном — полные выгрузки
```

- `run_once(notify_changes=False)` — не подтверждать приём (пакет останется в очереди 1С; сделано для отладки).
- `run_forever(interval, max_iterations=0)` — `max_iterations>0` ограничивает число итераций.

## Полная (первоначальная) выгрузка

При работе `run_forever` объекты, впервые встреченные в пакете изменений, автоматически ставятся в
очередь на полную выгрузку и грузятся фоновыми потоками. Можно запустить выгрузку и вручную:

Полная выгрузка объекта реализована на стороне python, чтобы поддержать выгрузку объектов больших размеров, 
т.к. если инициировать полную выгрузку по плану обмена в 1С, то данные поступят без возможности постраничной загрузки.
(Поэтому данная функция специально убрана из модуля 1С).

Полная выгрузка спроектирована так, чтобы работать параллельно с получением изменений объекта.

```python
rep.list_objects()             # имена объектов 1С, доступных для выгрузки (Catalog_…, Document_…, …)

rep.full_load("Catalog_Номенклатура")              
rep.full_load("Document_РеализацияТоваровУслуг", batch_size=500)
```

`batch_size` — верхняя граница, а не жёсткий размер страницы: реальный размер подбирается по весу
выданных страниц, потому что одна запись 1С может тянуть за собой и одну строку, и тысячи (все
табличные части документа, весь набор движений регистратора). Как именно устроена постраничная
выгрузка и почему keyset-курсор в 1С неприменим к ссылочным ключам — см.
[DOCUMENTATION.md](DOCUMENTATION.md).


### Фильтр по периоду

Для ручной догрузки за нужный период укажите поле даты/времени и границы (включительно):

```python
from datetime import date, datetime

# весь месяц: date-граница включает последний день целиком (даже для поля дата-время)
rep.full_load("Document_РеализацияТоваровУслуг",
              date_field="Date", date_from=date(2026, 6, 1), date_to=date(2026, 6, 30))

# точная граница по времени — передайте datetime
rep.full_load("Document_РеализацияТоваровУслуг",
              date_field="Date", date_from=datetime(2026, 6, 1, 9, 0, 0))
```

`date_field` — имя поля 1С (`Date` у документов, `Period` у регистров). Границы транслируются в OData
`$filter` и объединяются с курсором пагинации.

## Что появляется в целевой БД

- На каждый объект 1С — таблица (имя транслитерируется, длинные имена усекаются с хэшем под лимит СУБД).
- Служебные поля строк: `merged_on`/`inserted_on` (момент merge/первой вставки), `is_deleted_or_empty`
  (пометка удаления/пустой набор), `exchange_message_no` (номер пакета обмена).
- Служебные таблицы `replicator_1c_log` и `metadata_objects_1c` (см. ниже).

## Служебные таблицы

### `replicator_1c_log` — журнал загрузок

Строка на каждую загрузку объекта: пакет изменений или полная выгрузка.

| Колонка | Назначение |
|---|---|
| `id` | суррогатный ключ |
| `exchange` | имя плана обмена |
| `object` | имя объекта 1С |
| `type` | `changes` (пакет изменений) или `full` (полная выгрузка) |
| `message_no` | номер пакета обмена; `NULL` для полной выгрузки |
| `started_at` / `finished_at` | начало и конец загрузки; `finished_at IS NULL` — не завершена (упала) |
| `inserted_row_count` / `updated_row_count` / `deleted_row_count` | счётчики строк merge |
| `total_time` | суммарное время merge, сек |

Предназначено для мониторинга: незавершённые строки (`finished_at IS NULL`) — упавшие загрузки; по `type` и
`object` видно, что и когда грузилось.

### `metadata_objects_1c` — реестр объектов и состояние полной выгрузки

Синхронизируется с метаданными 1С — строка на каждый объект, встреченный в обмене. Ключ таблицы — полное
имя объекта (регистр и документ могут иметь одинаковое короткое имя).

| Колонка | Назначение |
|---|---|
| `object_full_name` | полное имя объекта 1С (ключ), например `Catalog_Номенклатура` |
| `object_full_name_en` | транслит = имя таблицы объекта в БД |
| `object_name` / `object_type` | имя и тип объекта (`Catalog` / `Document` / `AccumulationRegister` / …) |
| `fields` / `fields_en` | список полей объекта: имена 1С и их транслит (= колонки в БД) |
| `full_load_is_required` | объект ожидает полной выгрузки |
| `last_full_load_dt` | когда объект был полностью выгружен; `NULL` — ни разу |
| `merged_on` | момент синхронизации записи реестра |

Новый объект оркестратор помечает `full_load_is_required=true`, фоновый воркер выгружает его целиком и
проставляет `last_full_load_dt`. Отсюда же удобно посмотреть список доступных объектов и имена их таблиц.

## Логирование

Из коробки библиотека вешает вывод на логгер `cdc_1c` (INFO), если приложение не настроило логирование
само. Настроили своё — библиотека молчит и пишет через стандартный `logging`.

## Дальнейшая материализация и сборка денормализованных таблиц

1С хранит данные в нормализованном виде. Это значит, что чтобы дотянуться, например, 
из регистра заказов до кода товара, нужно делать JOIN с таблицей номенклатуры по идентификатору товара в виде guid.
В тоже время для задач DWH часто нужны данные с менее строгой нормализацией. 
Поэтому тут предложены примеры кода, как сделать инкрементное обновление последующей целевой таблицы с денормализованными данными:

[Вариант с сохранением guid ключа в таблице фактов](https://github.com/pavel-v-sobolev/cdc-1c/blob/main/materialize_example.py)
[Вариант с изменение ключа, использующий GROUP BY, но все равно оптимизированный и инкрементный](https://github.com/pavel-v-sobolev/cdc-1c/blob/main/materialize_example_other_key.py)

