cdc-1c 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cdc_1c-0.1.0/.github/workflows/ci.yml +37 -0
- cdc_1c-0.1.0/.gitignore +215 -0
- cdc_1c-0.1.0/CHANGELOG.md +23 -0
- cdc_1c-0.1.0/LICENSE +21 -0
- cdc_1c-0.1.0/PKG-INFO +200 -0
- cdc_1c-0.1.0/PLAN.md +124 -0
- cdc_1c-0.1.0/README.md +163 -0
- cdc_1c-0.1.0/cdc-1c-form-module.txt +690 -0
- cdc_1c-0.1.0/cdc-1c.epf +0 -0
- cdc_1c-0.1.0/main.py +46 -0
- cdc_1c-0.1.0/materialize_example.py +57 -0
- cdc_1c-0.1.0/materialize_example_other_key.py +75 -0
- cdc_1c-0.1.0/pyproject.toml +74 -0
- cdc_1c-0.1.0/src/cdc_1c/__init__.py +18 -0
- cdc_1c-0.1.0/src/cdc_1c/__main__.py +30 -0
- cdc_1c-0.1.0/src/cdc_1c/change_reader.py +77 -0
- cdc_1c-0.1.0/src/cdc_1c/common_functions.py +30 -0
- cdc_1c-0.1.0/src/cdc_1c/config.py +41 -0
- cdc_1c-0.1.0/src/cdc_1c/data_reader.py +452 -0
- cdc_1c-0.1.0/src/cdc_1c/db_logs.py +103 -0
- cdc_1c-0.1.0/src/cdc_1c/db_writer.py +180 -0
- cdc_1c-0.1.0/src/cdc_1c/logging_config.py +23 -0
- cdc_1c-0.1.0/src/cdc_1c/metadata_reader.py +388 -0
- cdc_1c-0.1.0/src/cdc_1c/name_mapper.py +94 -0
- cdc_1c-0.1.0/src/cdc_1c/py.typed +0 -0
- cdc_1c-0.1.0/src/cdc_1c/replicator.py +377 -0
- cdc_1c-0.1.0/tests/env_latest.sh +6 -0
- cdc_1c-0.1.0/tests/fake_1c.py +130 -0
- cdc_1c-0.1.0/tests/record_1c.py +135 -0
- cdc_1c-0.1.0/tests/responses/trade_demo_8.5/manifest.json +12 -0
- cdc_1c-0.1.0/tests/responses/trade_demo_8.5/msg001_metadata.xml +6964 -0
- cdc_1c-0.1.0/tests/responses/trade_demo_8.5/msg002_nomenklatura.xml +664 -0
- cdc_1c-0.1.0/tests/responses/trade_demo_8.5/msg003_zakaz_klienta.xml +2113 -0
- cdc_1c-0.1.0/tests/responses/trade_demo_8.5/msg004_kontragenti.xml +322 -0
- cdc_1c-0.1.0/tests/responses/trade_demo_8.5/msg005_zakaz_klienta_2.xml +243 -0
- cdc_1c-0.1.0/tests/responses/trade_demo_8.5/msg006_realizatsiya.xml +2741 -0
- cdc_1c-0.1.0/tests/test_cdc_run_once.py +58 -0
- cdc_1c-0.1.0/tests/test_entrypoint.py +67 -0
- cdc_1c-0.1.0/tests/test_full_load.py +323 -0
- cdc_1c-0.1.0/tests/test_json_export.py +101 -0
- cdc_1c-0.1.0/tests/test_metadata_objects.py +93 -0
- cdc_1c-0.1.0/tests/test_pipeline_replay.py +82 -0
- cdc_1c-0.1.0/tests/test_run_forever_live.py +44 -0
- cdc_1c-0.1.0/tests/test_version_guard.py +118 -0
- cdc_1c-0.1.0/uv.lock +622 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
tags: ["v*"]
|
|
7
|
+
pull_request:
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- uses: astral-sh/setup-uv@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: ${{ matrix.python-version }}
|
|
20
|
+
- run: uv sync --extra dev
|
|
21
|
+
# integration-тесты (живая 1С/Postgres) отсеиваются addopts из pyproject.
|
|
22
|
+
- run: uv run pytest -q
|
|
23
|
+
|
|
24
|
+
publish:
|
|
25
|
+
# Публикация на PyPI по git-тегу vX.Y.Z. Требует настроенного Trusted Publishing на стороне PyPI
|
|
26
|
+
# (проект + этот репозиторий/workflow) — секреты/токены не нужны.
|
|
27
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
28
|
+
needs: test
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
environment: pypi
|
|
31
|
+
permissions:
|
|
32
|
+
id-token: write
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@v4
|
|
35
|
+
- uses: astral-sh/setup-uv@v5
|
|
36
|
+
- run: uv build
|
|
37
|
+
- run: uv publish --trusted-publishing always
|
cdc_1c-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# Claude Code local settings
|
|
2
|
+
.claude/
|
|
3
|
+
|
|
4
|
+
# Byte-compiled / optimized / DLL files
|
|
5
|
+
__pycache__/
|
|
6
|
+
*.py[codz]
|
|
7
|
+
*$py.class
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
.vscode
|
|
12
|
+
.qwen
|
|
13
|
+
|
|
14
|
+
# C extensions
|
|
15
|
+
*.so
|
|
16
|
+
|
|
17
|
+
# Distribution / packaging
|
|
18
|
+
.Python
|
|
19
|
+
build/
|
|
20
|
+
develop-eggs/
|
|
21
|
+
dist/
|
|
22
|
+
downloads/
|
|
23
|
+
eggs/
|
|
24
|
+
.eggs/
|
|
25
|
+
lib/
|
|
26
|
+
lib64/
|
|
27
|
+
parts/
|
|
28
|
+
sdist/
|
|
29
|
+
var/
|
|
30
|
+
wheels/
|
|
31
|
+
share/python-wheels/
|
|
32
|
+
*.egg-info/
|
|
33
|
+
.installed.cfg
|
|
34
|
+
*.egg
|
|
35
|
+
MANIFEST
|
|
36
|
+
|
|
37
|
+
# PyInstaller
|
|
38
|
+
# Usually these files are written by a python script from a template
|
|
39
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
40
|
+
*.manifest
|
|
41
|
+
*.spec
|
|
42
|
+
|
|
43
|
+
# Installer logs
|
|
44
|
+
pip-log.txt
|
|
45
|
+
pip-delete-this-directory.txt
|
|
46
|
+
|
|
47
|
+
# Unit test / coverage reports
|
|
48
|
+
htmlcov/
|
|
49
|
+
.tox/
|
|
50
|
+
.nox/
|
|
51
|
+
.coverage
|
|
52
|
+
.coverage.*
|
|
53
|
+
.cache
|
|
54
|
+
nosetests.xml
|
|
55
|
+
coverage.xml
|
|
56
|
+
*.cover
|
|
57
|
+
*.py.cover
|
|
58
|
+
.hypothesis/
|
|
59
|
+
.pytest_cache/
|
|
60
|
+
cover/
|
|
61
|
+
|
|
62
|
+
# Translations
|
|
63
|
+
*.mo
|
|
64
|
+
*.pot
|
|
65
|
+
|
|
66
|
+
# Django stuff:
|
|
67
|
+
*.log
|
|
68
|
+
local_settings.py
|
|
69
|
+
db.sqlite3
|
|
70
|
+
db.sqlite3-journal
|
|
71
|
+
|
|
72
|
+
# Flask stuff:
|
|
73
|
+
instance/
|
|
74
|
+
.webassets-cache
|
|
75
|
+
|
|
76
|
+
# Scrapy stuff:
|
|
77
|
+
.scrapy
|
|
78
|
+
|
|
79
|
+
# Sphinx documentation
|
|
80
|
+
docs/_build/
|
|
81
|
+
|
|
82
|
+
# PyBuilder
|
|
83
|
+
.pybuilder/
|
|
84
|
+
target/
|
|
85
|
+
|
|
86
|
+
# Jupyter Notebook
|
|
87
|
+
.ipynb_checkpoints
|
|
88
|
+
|
|
89
|
+
# IPython
|
|
90
|
+
profile_default/
|
|
91
|
+
ipython_config.py
|
|
92
|
+
|
|
93
|
+
# pyenv
|
|
94
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
95
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
96
|
+
# .python-version
|
|
97
|
+
|
|
98
|
+
# pipenv
|
|
99
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
100
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
101
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
102
|
+
# install all needed dependencies.
|
|
103
|
+
#Pipfile.lock
|
|
104
|
+
|
|
105
|
+
# UV
|
|
106
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
107
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
108
|
+
# commonly ignored for libraries.
|
|
109
|
+
#uv.lock
|
|
110
|
+
|
|
111
|
+
# poetry
|
|
112
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
113
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
114
|
+
# commonly ignored for libraries.
|
|
115
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
116
|
+
#poetry.lock
|
|
117
|
+
#poetry.toml
|
|
118
|
+
|
|
119
|
+
# pdm
|
|
120
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
121
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
122
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
123
|
+
#pdm.lock
|
|
124
|
+
#pdm.toml
|
|
125
|
+
.pdm-python
|
|
126
|
+
.pdm-build/
|
|
127
|
+
|
|
128
|
+
# pixi
|
|
129
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
130
|
+
#pixi.lock
|
|
131
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
132
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
133
|
+
.pixi
|
|
134
|
+
|
|
135
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
136
|
+
__pypackages__/
|
|
137
|
+
|
|
138
|
+
# Celery stuff
|
|
139
|
+
celerybeat-schedule
|
|
140
|
+
celerybeat.pid
|
|
141
|
+
|
|
142
|
+
# SageMath parsed files
|
|
143
|
+
*.sage.py
|
|
144
|
+
|
|
145
|
+
# Environments
|
|
146
|
+
.env
|
|
147
|
+
.envrc
|
|
148
|
+
.venv
|
|
149
|
+
env/
|
|
150
|
+
venv/
|
|
151
|
+
ENV/
|
|
152
|
+
env.bak/
|
|
153
|
+
venv.bak/
|
|
154
|
+
|
|
155
|
+
# Spyder project settings
|
|
156
|
+
.spyderproject
|
|
157
|
+
.spyproject
|
|
158
|
+
|
|
159
|
+
# Rope project settings
|
|
160
|
+
.ropeproject
|
|
161
|
+
|
|
162
|
+
# mkdocs documentation
|
|
163
|
+
/site
|
|
164
|
+
|
|
165
|
+
# mypy
|
|
166
|
+
.mypy_cache/
|
|
167
|
+
.dmypy.json
|
|
168
|
+
dmypy.json
|
|
169
|
+
|
|
170
|
+
# Pyre type checker
|
|
171
|
+
.pyre/
|
|
172
|
+
|
|
173
|
+
# pytype static type analyzer
|
|
174
|
+
.pytype/
|
|
175
|
+
|
|
176
|
+
# Cython debug symbols
|
|
177
|
+
cython_debug/
|
|
178
|
+
|
|
179
|
+
# PyCharm
|
|
180
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
181
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
182
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
183
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
184
|
+
#.idea/
|
|
185
|
+
|
|
186
|
+
# Abstra
|
|
187
|
+
# Abstra is an AI-powered process automation framework.
|
|
188
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
189
|
+
# Learn more at https://abstra.io/docs
|
|
190
|
+
.abstra/
|
|
191
|
+
|
|
192
|
+
# Visual Studio Code
|
|
193
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
194
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
195
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
196
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
197
|
+
# .vscode/
|
|
198
|
+
|
|
199
|
+
# Ruff stuff:
|
|
200
|
+
.ruff_cache/
|
|
201
|
+
|
|
202
|
+
# PyPI configuration file
|
|
203
|
+
.pypirc
|
|
204
|
+
|
|
205
|
+
# Cursor
|
|
206
|
+
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
|
|
207
|
+
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
|
|
208
|
+
# refer to https://docs.cursor.com/context/ignore-files
|
|
209
|
+
.cursorignore
|
|
210
|
+
.cursorindexingignore
|
|
211
|
+
|
|
212
|
+
# Marimo
|
|
213
|
+
marimo/_static/
|
|
214
|
+
marimo/_lsp/
|
|
215
|
+
__marimo__/
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Формат — [Keep a Changelog](https://keepachangelog.com/ru/1.1.0/),
|
|
4
|
+
версионирование — [SemVer](https://semver.org/lang/ru/).
|
|
5
|
+
|
|
6
|
+
## [0.1.0] — Unreleased
|
|
7
|
+
|
|
8
|
+
Первый публичный релиз (Development Status :: Alpha).
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Оркестратор `Replicator1C`: чтение изменений из 1С (OData + план обмена) и upsert в целевую БД
|
|
12
|
+
через `dbmerge`; подтверждение приёма пакета только после успешного сохранения (`run_once` /
|
|
13
|
+
`run_forever` с graceful SIGTERM/SIGINT).
|
|
14
|
+
- Полная выгрузка `full_load`: keyset-пагинация (Ref_Key / Recorder / составной ключ независимого
|
|
15
|
+
регистра), фильтр по периоду (`date_field` + `date_from`/`date_to`), фоновые выгрузки в
|
|
16
|
+
`run_forever`.
|
|
17
|
+
- Version-guard полной выгрузки (`exchange_message_no`): устаревший снимок не затирает более свежие
|
|
18
|
+
изменения и не воскрешает удалённые строки групп (регистр/табличная часть).
|
|
19
|
+
- Транслитерация имён и лимиты идентификаторов (`NameMapper1C`); спец-поля `merged_on`,
|
|
20
|
+
`inserted_on`, `is_deleted_or_empty`, `exchange_message_no`.
|
|
21
|
+
- Служебные таблицы: журнал загрузок `replicator_1c_log`, реестр объектов `metadata_objects_1c`.
|
|
22
|
+
|
|
23
|
+
[0.1.0]: https://github.com/pavel-v-sobolev/cdc_1C/releases/tag/v0.1.0
|
cdc_1c-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Pavel Sobolev
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
cdc_1c-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cdc-1c
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Change data capture (CDC) from 1C:Enterprise to your data warehouse
|
|
5
|
+
Project-URL: Homepage, https://github.com/pavel-v-sobolev/cdc_1C
|
|
6
|
+
Project-URL: Repository, https://github.com/pavel-v-sobolev/cdc_1C
|
|
7
|
+
Project-URL: Issues, https://github.com/pavel-v-sobolev/cdc_1C/issues
|
|
8
|
+
Author-email: Pavel Sobolev <pavel-v-sobolev@yandex.ru>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: 1c,1c-enterprise,cdc,data-engineering,dwh,etl,postgres,python
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Information Technology
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: Intended Audience :: System Administrators
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
25
|
+
Classifier: Topic :: Database
|
|
26
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
27
|
+
Requires-Python: >=3.10
|
|
28
|
+
Requires-Dist: dbmerge>=1.0.20
|
|
29
|
+
Requires-Dist: requests>=2.33.0
|
|
30
|
+
Requires-Dist: sqlalchemy>=2.0.49
|
|
31
|
+
Requires-Dist: xmltodict>=1.0.4
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
34
|
+
Provides-Extra: postgres
|
|
35
|
+
Requires-Dist: psycopg2-binary>=2.9.12; extra == 'postgres'
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
|
|
38
|
+
**cdc-1c** is a docker container and a Python library, that provides 1C system data loading to data warehouse using Change Data Capture apporach. \
|
|
39
|
+
It engages standard ODATA mechanism and standard 1C exchange plan mechanism to extract data from 1C system and upsert changes to the target DB.
|
|
40
|
+
|
|
41
|
+
**cdc-1c** - это докер контейнер и python-библиотека, предназначенные для получения данных из 1С, использующий подход CDC (загрузка изменений данных). \
|
|
42
|
+
Продукт использует стандартный интерфейс ODATA и механизм планов обмена для выгрузки изменений данных из системы 1С и обновления данных в целевой БД.
|
|
43
|
+
|
|
44
|
+
# Что нужно для работы
|
|
45
|
+
1) опубликовать базу 1с на web
|
|
46
|
+
2) создать пользователя odata и дать ему необходимые права
|
|
47
|
+
3) настроить план обмена в конфигураторе и включить в его состав нужные объекты 1с
|
|
48
|
+
3) настроить узел обмена с использованием внешней обработки `cdc-1c.odt`
|
|
49
|
+
4) поднять docker контейнер (плока не доступен - todo) для получения данных или адаптировать под свой вариант, исспользуя библиотеку python cdc-1c
|
|
50
|
+
|
|
51
|
+
# Использование библиотеки python
|
|
52
|
+
|
|
53
|
+
Библиотека даёт оркестратор `Replicator1C`, который читает изменения из 1С (OData + план обмена) и
|
|
54
|
+
складывает их в целевую БД, подтверждая приём пакета только после успешного сохранения. БД
|
|
55
|
+
передаётся готовым SQLAlchemy `engine`.
|
|
56
|
+
|
|
57
|
+
## Установка
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pip install cdc-1c
|
|
61
|
+
|
|
62
|
+
# c драйвером PostgreSQL (тестовая СУБД):
|
|
63
|
+
pip install "cdc-1c[postgres]"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Поддерживается Python 3.10+. Модуль тестировался на PostgreSQL, но целевой может быть любая БД из
|
|
67
|
+
числа поддерживаемых модулем `dbmerge` (через него идёт запись). Сама библиотека драйвер БД не
|
|
68
|
+
импортирует — его ставите под свою СУБД (для PostgreSQL — extra `[postgres]` выше).
|
|
69
|
+
|
|
70
|
+
## Быстрый старт
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from sqlalchemy import create_engine
|
|
74
|
+
from cdc_1c import Replicator1C
|
|
75
|
+
|
|
76
|
+
engine = create_engine("postgresql+psycopg2://user:pass@localhost:5432/dwh", pool_size=5)
|
|
77
|
+
|
|
78
|
+
rep = Replicator1C(
|
|
79
|
+
odata_url="http://host/base/odata/standard.odata",
|
|
80
|
+
odata_auth=("odata", "secret"), # (user, password) либо None без авторизации
|
|
81
|
+
exchange_name="ДляODATA", # имя плана обмена в 1С
|
|
82
|
+
queue_guid="a9bc23c5-3689-11f1-926c-0800270bc6cb", # Ref_Key узла обмена (очереди)
|
|
83
|
+
engine=engine,
|
|
84
|
+
db_schema="cdc_1c", # None → схема БД по умолчанию (public у Postgres)
|
|
85
|
+
request_timeout=60, # таймаут HTTP-запросов к 1С, сек (по умолчанию = interval)
|
|
86
|
+
full_load_workers=2, # число фоновых потоков полной выгрузки
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
rep.run_forever(interval=60) # цикл опроса раз в 60 секунд
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
## Режимы: `run_once` и `run_forever`
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
rep.run_once() # один цикл: read → save → notify (подтверждение только после save)
|
|
97
|
+
rep.run_forever(interval=60) # бесконечный цикл run_once с паузой; фоном — полные выгрузки
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
- `run_once(notify_changes=False)` — не подтверждать приём (пакет останется в очереди 1С; сделано для отладки).
|
|
101
|
+
- `run_forever(interval, max_iterations=0)` — `max_iterations>0` ограничивает число итераций.
|
|
102
|
+
|
|
103
|
+
## Полная (первоначальная) выгрузка
|
|
104
|
+
|
|
105
|
+
При работе `run_forever` объекты, впервые встреченные в пакете изменений, автоматически ставятся в
|
|
106
|
+
очередь на полную выгрузку и грузятся фоновыми потоками. Можно запустить выгрузку и вручную:
|
|
107
|
+
|
|
108
|
+
Полная выгрузка объекта реализована на стороне python, чтобы поддержать выгрузку объектов больших размеров,
|
|
109
|
+
т.к. если инициировать полную выгрузку по плану обмена в 1С, то данные поступят без возможности постраничной загрузки.
|
|
110
|
+
(Поэтому данная функция специально убрана из модуля 1С).
|
|
111
|
+
|
|
112
|
+
Полная выгрузка спроектирована так, чтобы работать параллельно с получением изменений объекта.
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
rep.list_objects() # имена объектов 1С, доступных для выгрузки (Catalog_…, Document_…, …)
|
|
116
|
+
|
|
117
|
+
rep.full_load("Catalog_Номенклатура")
|
|
118
|
+
rep.full_load("Document_РеализацияТоваровУслуг", batch_size=500)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
### Фильтр по периоду
|
|
123
|
+
|
|
124
|
+
Для ручной догрузки за нужный период укажите поле даты/времени и границы (включительно):
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from datetime import date, datetime
|
|
128
|
+
|
|
129
|
+
# весь месяц: date-граница включает последний день целиком (даже для поля дата-время)
|
|
130
|
+
rep.full_load("Document_РеализацияТоваровУслуг",
|
|
131
|
+
date_field="Date", date_from=date(2026, 6, 1), date_to=date(2026, 6, 30))
|
|
132
|
+
|
|
133
|
+
# точная граница по времени — передайте datetime
|
|
134
|
+
rep.full_load("Document_РеализацияТоваровУслуг",
|
|
135
|
+
date_field="Date", date_from=datetime(2026, 6, 1, 9, 0, 0))
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
`date_field` — имя поля 1С (`Date` у документов, `Period` у регистров). Границы транслируются в OData
|
|
139
|
+
`$filter` и объединяются с курсором пагинации.
|
|
140
|
+
|
|
141
|
+
## Что появляется в целевой БД
|
|
142
|
+
|
|
143
|
+
- На каждый объект 1С — таблица (имя транслитерируется, длинные имена усекаются с хэшем под лимит СУБД).
|
|
144
|
+
- Служебные поля строк: `merged_on`/`inserted_on` (момент merge/первой вставки), `is_deleted_or_empty`
|
|
145
|
+
(пометка удаления/пустой набор), `exchange_message_no` (номер пакета обмена).
|
|
146
|
+
- Служебные таблицы `replicator_1c_log` и `metadata_objects_1c` (см. ниже).
|
|
147
|
+
|
|
148
|
+
## Служебные таблицы
|
|
149
|
+
|
|
150
|
+
### `replicator_1c_log` — журнал загрузок
|
|
151
|
+
|
|
152
|
+
Строка на каждую загрузку объекта: пакет изменений или полная выгрузка.
|
|
153
|
+
|
|
154
|
+
| Колонка | Назначение |
|
|
155
|
+
|---|---|
|
|
156
|
+
| `id` | суррогатный ключ |
|
|
157
|
+
| `exchange` | имя плана обмена |
|
|
158
|
+
| `object` | имя объекта 1С |
|
|
159
|
+
| `type` | `changes` (пакет изменений) или `full` (полная выгрузка) |
|
|
160
|
+
| `message_no` | номер пакета обмена; `NULL` для полной выгрузки |
|
|
161
|
+
| `started_at` / `finished_at` | начало и конец загрузки; `finished_at IS NULL` — не завершена (упала) |
|
|
162
|
+
| `inserted_row_count` / `updated_row_count` / `deleted_row_count` | счётчики строк merge |
|
|
163
|
+
| `total_time` | суммарное время merge, сек |
|
|
164
|
+
|
|
165
|
+
Предназначено для мониторинга: незавершённые строки (`finished_at IS NULL`) — упавшие загрузки; по `type` и
|
|
166
|
+
`object` видно, что и когда грузилось.
|
|
167
|
+
|
|
168
|
+
### `metadata_objects_1c` — реестр объектов и состояние полной выгрузки
|
|
169
|
+
|
|
170
|
+
Синхронизируется с метаданными 1С — строка на каждый объект, встреченный в обмене. Ключ таблицы — полное
|
|
171
|
+
имя объекта (регистр и документ могут иметь одинаковое короткое имя).
|
|
172
|
+
|
|
173
|
+
| Колонка | Назначение |
|
|
174
|
+
|---|---|
|
|
175
|
+
| `object_full_name` | полное имя объекта 1С (ключ), например `Catalog_Номенклатура` |
|
|
176
|
+
| `object_full_name_en` | транслит = имя таблицы объекта в БД |
|
|
177
|
+
| `object_name` / `object_type` | имя и тип объекта (`Catalog` / `Document` / `AccumulationRegister` / …) |
|
|
178
|
+
| `fields` / `fields_en` | список полей объекта: имена 1С и их транслит (= колонки в БД) |
|
|
179
|
+
| `full_load_is_required` | объект ожидает полной выгрузки |
|
|
180
|
+
| `last_full_load_dt` | когда объект был полностью выгружен; `NULL` — ни разу |
|
|
181
|
+
| `merged_on` | момент синхронизации записи реестра |
|
|
182
|
+
|
|
183
|
+
Новый объект оркестратор помечает `full_load_is_required=true`, фоновый воркер выгружает его целиком и
|
|
184
|
+
проставляет `last_full_load_dt`. Отсюда же удобно посмотреть список доступных объектов и имена их таблиц.
|
|
185
|
+
|
|
186
|
+
## Логирование
|
|
187
|
+
|
|
188
|
+
Из коробки библиотека вешает вывод на логгер `cdc_1c` (INFO), если приложение не настроило логирование
|
|
189
|
+
само. Настроили своё — библиотека молчит и пишет через стандартный `logging`.
|
|
190
|
+
|
|
191
|
+
## Дальнейшая материализация и сборка денормализованных таблиц
|
|
192
|
+
|
|
193
|
+
1С хранит данные в нормализованном виде. Это значит, что чтобы дотянуться, например,
|
|
194
|
+
из регистра заказов до кода товара, нужно делать JOIN с таблицей номенклатуры по идентификатору товара в виде guid.
|
|
195
|
+
В тоже время для задач DWH часто нужны данные с менее строгой нормализацией.
|
|
196
|
+
Поэтому тут предложены примеры кода, как сделать инкрементное обновление последующей целевой таблицы с денормализованными данными:
|
|
197
|
+
|
|
198
|
+
[Вариант с сохранением guid ключа в таблице фактов](https://github.com/pavel-v-sobolev/cdc-1c/blob/main/materialize_example.py)
|
|
199
|
+
[Вариант с изменение ключа, использующий GROUP BY, но все равно оптимизированный и инкрементный](https://github.com/pavel-v-sobolev/cdc-1c/blob/main/materialize_example_other_key.py)
|
|
200
|
+
|
cdc_1c-0.1.0/PLAN.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# План проекта cdc-1c
|
|
2
|
+
|
|
3
|
+
## Context
|
|
4
|
+
|
|
5
|
+
Ядро CDC из 1С (OData → БД через dbmerge) реализовано и проверено на живой 1С + PostgreSQL:
|
|
6
|
+
чтение изменений (`ChangeReader1C`), разбор в колоночные `DataObject1C`, транслитерация и лимиты
|
|
7
|
+
имён (`NameMapper1C`), типы/ключи/`delete_key` из метаданных (`MetadataReader1C`), пер-объектное
|
|
8
|
+
сохранение со scoped-delete для регистров/ТЧ (`DBWriter1C`), спец-поля `is_deleted_or_empty` и
|
|
9
|
+
`exchange_message_no`.
|
|
10
|
+
|
|
11
|
+
Текущая цель: оформить наработку как opensource-продукт — Python-пакет на PyPI + Docker-образ для
|
|
12
|
+
запуска «из коробки» с минимумом настроек, и удобный оркестратор с простым вызовом.
|
|
13
|
+
|
|
14
|
+
## Решения
|
|
15
|
+
- Имя: дистрибутив **cdc-1c**, import-пакет **cdc_1c** (нижний регистр); классы PascalCase.
|
|
16
|
+
- Оркестратор: класс **`Replicator1C`** (суффикс `1C` — как у остальных публичных классов).
|
|
17
|
+
- Конструктор: **отдельные именованные аргументы** (не объект `Config`) — прямой библиотечный
|
|
18
|
+
вызов без обёртки, единый стиль с прочими классами. БД передаётся готовым **`engine`** (DI по-
|
|
19
|
+
sqlalchemy'ному, тестируемость, переиспользование), а не строкой; тот же engine идёт в `DBWriter1C`.
|
|
20
|
+
- `Config` (dataclass) — env-сторона: хранит `db_url`; classmethod **`Replicator1C.from_config(cfg)`**
|
|
21
|
+
строит engine из `db_url` (граница «строка → engine» в одном месте) и маппит поля. `poll_interval`/
|
|
22
|
+
`log_level` остаются в `Config` для `run_forever`/логирования. Entrypoint: `Config.from_env()`.
|
|
23
|
+
- Режимы оркестратора: `run_once()` и `run_forever(interval)`.
|
|
24
|
+
- Python: **>=3.10**. Лицензия: **MIT** (предварительно).
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Сделано (ядро CDC)
|
|
29
|
+
|
|
30
|
+
- **`_get_register_records` / удаление наборов** — `_default_key_value`, `_make_deleted_register_record`:
|
|
31
|
+
при пустом `RecordSet` запись дополняется полным ключом из метаданных (дефолты) + реальные
|
|
32
|
+
`Recorder`/`Recorder_Type`.
|
|
33
|
+
- **`NameMapper1C`** — транслитерация; лимит `POSTGRES_MAX_IDENTIFIER = 63` (обрезка + 4-симв. хэш);
|
|
34
|
+
служебные имена `RESERVED_FIELD_NAMES` (`merged_on`, `inserted_on`, `exchange_message_no`): наше
|
|
35
|
+
служебное поле сохраняет имя, поле 1С с таким же транслитом хэшируется; журнал
|
|
36
|
+
`object_mappings`/`field_mappings`. Ручной маппинг убран.
|
|
37
|
+
- **`DataReader1C`** — `DataObject1C.to_records_mapped()` (dict-of-lists → list-of-dict, маппинг
|
|
38
|
+
колонок на лету, без копии); `_convert_value` для `Guid` → `uuid.UUID`.
|
|
39
|
+
- **`DBWriter1C`** — пер-объектное сохранение; удаление по `metadata_obj.delete_key`: документ/
|
|
40
|
+
справочник → `delete_mode='no'`; регистр/ТЧ → `delete_mode='delete'` со scoped `delete_condition`
|
|
41
|
+
(`_scoped_delete_condition`: подзапрос из temp-таблицы — `col IN (SELECT col FROM temp)` или
|
|
42
|
+
row-value `tuple_(...).in_(select(...))`). `Config`/`data_reader` в конструкторе, `save_all()`.
|
|
43
|
+
- **`MetadataReader1C`** — `MetadataObject1C.delete_key` + `_get_delete_key` (регистр →
|
|
44
|
+
`Recorder`/`Recorder_Type`; ТЧ → `Ref_Key`; документ/справочник → None).
|
|
45
|
+
- **Табличные части** — строкам ТЧ проставляется `Ref_Key` владельца (в данных 1С его нет);
|
|
46
|
+
пустая ТЧ → фиктивная запись (`_make_empty_table_part_record`) для scoped-delete по `Ref_Key`.
|
|
47
|
+
- **Спец-поля** — `is_deleted_or_empty` (Boolean: `DeletionMark` документа/справочника, проброс
|
|
48
|
+
пометки в строки ТЧ, `True` у фиктивных записей) и `exchange_message_no` (номер пакета обмена,
|
|
49
|
+
во всех записях; ставится в `ChangeReader.read_changes`).
|
|
50
|
+
- **A1. Переименование** — `src/cdc_1C` → `src/cdc_1c`, все импорты `cdc_1C` → `cdc_1c`,
|
|
51
|
+
`pyproject` `name = "cdc-1c"`, `version("cdc-1c")`. Пересобрано `uv sync` (cdc-1c==0.1.0).
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Осталось
|
|
56
|
+
|
|
57
|
+
### Фаза A. Hardening ядра
|
|
58
|
+
- **A2. Конфигурируемый auth** — ✅ (частично) `MetadataReader1C`/`DataReader1C.__init__` принимают
|
|
59
|
+
`auth: tuple[str,str] | None`, хранят `self.auth`; все `requests.*` используют `auth=self.auth`;
|
|
60
|
+
`ChangeReader1C` пробрасывает `auth`. Захардкоженные `('admin','admin')` убраны (4 места).
|
|
61
|
+
Осталось: общий `requests.Session`, `timeout`, опц. `verify`.
|
|
62
|
+
- **A3. Сброс состояния** — ✅ `ChangeReader1C.read_changes()` в начале делает `self.clear()`.
|
|
63
|
+
- **A4. `Config` (dataclass)** — ✅ (частично) `src/cdc_1c/config.py`: `odata_url`, `odata_user`,
|
|
64
|
+
`odata_password`, `exchange_name`, `queue_guid`, `db_url`, `db_schema`, `poll_interval`, `log_level`;
|
|
65
|
+
`from_env()` читает `CDC1C_*`. Осталось: опц. `request_timeout`, `verify_ssl` (вместе с остатком A2).
|
|
66
|
+
- **A5. Оркестратор `Replicator1C`** — ✅ `src/cdc_1c/replicator.py`: собирает
|
|
67
|
+
metadata/changes/mapper/writer из отдельных аргументов (engine + auth из user/password); classmethod
|
|
68
|
+
`from_config(Config)` строит engine из `db_url`; `run_once()` (read → save → **notify только после
|
|
69
|
+
успешного save**); `run_forever(interval)` с обработкой исключений (упал цикл → лог, без notify,
|
|
70
|
+
повтор) и graceful SIGTERM/SIGINT (`_StopSignal`). Экспортирован из `cdc_1c`.
|
|
71
|
+
- **A6. Логирование** — ✅ во всех модулях `logger = logging.getLogger(__name__)`, `basicConfig()`/root
|
|
72
|
+
убраны (ридеры/writer/replicator/config). Авто-вывод из коробки — `src/cdc_1c/logging_config.py`
|
|
73
|
+
`_ensure_handler()` (вешает StreamHandler на логгер `cdc_1c` с INFO, только если `hasHandlers()` ==
|
|
74
|
+
False), вызывается из `Replicator1C.__init__`. Если приложение настроило логирование — молчим.
|
|
75
|
+
Уровень из `Config.log_level` — настраивать в entrypoint (B2). Проверено: на чистом root INFO виден
|
|
76
|
+
из коробки, при настроенном приложением логировании — молчим. Зависимость **dbmerge** переведена на
|
|
77
|
+
тот же паттерн (`getLogger('dbmerge')` + `hasHandlers()`, без `basicConfig`) — root больше не
|
|
78
|
+
загрязняется, обе библиотеки сосуществуют чисто.
|
|
79
|
+
- ✅ Файлы модулей в snake_case (`metadata_reader.py`, `data_reader.py`, `change_reader.py`,
|
|
80
|
+
`name_mapper.py`, `db_writer.py`); классы и реэкспорт из `__init__` не изменены, публичный API
|
|
81
|
+
(`from cdc_1c import …`) прежний.
|
|
82
|
+
|
|
83
|
+
### Фаза B. Упаковка для PyPI
|
|
84
|
+
- **B1. `pyproject.toml`** — описание, `license = "MIT"`, авторы, `requires-python = ">=3.10"`,
|
|
85
|
+
keywords/classifiers/urls; ядро: `dbmerge`, `requests`, `sqlalchemy`, `xmltodict` (убрать `polars`
|
|
86
|
+
и `psycopg2` из обязательных); `[project.optional-dependencies] postgres = ["psycopg[binary]>=3.2"]`
|
|
87
|
+
(psycopg3, DSN `postgresql+psycopg`); `[project.scripts] cdc-1c = "cdc_1c.__main__:main"`.
|
|
88
|
+
- **B2. Entrypoint** — `src/cdc_1c/__main__.py`: `Config.from_env()` → `Replicator`; режим
|
|
89
|
+
`CDC1C_MODE=once|loop` (по умолчанию loop). Работает как `python -m cdc_1c` и команда `cdc-1c`.
|
|
90
|
+
- **B3.** `LICENSE`, `src/cdc_1c/py.typed`, `CHANGELOG.md`; английский README (quickstart pip+Docker,
|
|
91
|
+
таблица ENV, требования к плану обмена OData в 1С, поведение имён, спец-поля, ограничения);
|
|
92
|
+
`main.py` → краткий пример использования `Replicator` (или удалить).
|
|
93
|
+
|
|
94
|
+
### Фаза C. Docker «из коробки»
|
|
95
|
+
- **C1. `Dockerfile`** — `python:3.12-slim`, `cdc-1c[postgres]`, `ENTRYPOINT ["cdc-1c"]`, ENV,
|
|
96
|
+
graceful SIGTERM.
|
|
97
|
+
- **C2. `docker-compose.yml`** — сервис `cdc-1c` + опциональный `postgres`; `.env.example`;
|
|
98
|
+
README-раздел «Запуск в Docker за 1 минуту».
|
|
99
|
+
|
|
100
|
+
### Фаза D. Качество
|
|
101
|
+
- **D1. Тесты (pytest)** — `NameMapper1C`, `to_records_mapped`, `_get_delete_key`; на sqlite —
|
|
102
|
+
`DBWriter1C` scoped-delete (регистр/ТЧ/пустая ТЧ), идемпотентность, фиктивные записи,
|
|
103
|
+
`is_deleted_or_empty` по всем веткам, GUID→`uuid.UUID`; ридеры — фикстуры реального XML
|
|
104
|
+
`SelectChanges`/`$metadata`.
|
|
105
|
+
- **D2. CI (GitHub Actions)** — ruff, тесты на матрице 3.10–3.13, сборка; публикация на PyPI по
|
|
106
|
+
git-тегу (OIDC/trusted publishing).
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Известные ограничения (→ README)
|
|
111
|
+
- Фиктивные записи (удалённый набор регистра, пустая ТЧ) вставляются как заглушка с дефолтным
|
|
112
|
+
ключом (`LineNumber=0`); реальные старые строки удаляются scoped-delete'ом. Отличаются по
|
|
113
|
+
`is_deleted_or_empty=True`.
|
|
114
|
+
- Опустевшая ТЧ как `xsi:nil` сейчас отфильтровывается (`_get_record_table_parts`) — уточнить формат
|
|
115
|
+
1С и допокрыть при необходимости.
|
|
116
|
+
- Уникальность обрезанных длинных имён строго не гарантируется (хэш от полного имени).
|
|
117
|
+
|
|
118
|
+
## Проверка (end-to-end)
|
|
119
|
+
1. `uv sync`; `pytest` — зелёные offline-тесты (без 1С/Postgres).
|
|
120
|
+
2. `Config(...)` → `Replicator(cfg).run_once()` против живой 1С + Postgres: таблицы, типы,
|
|
121
|
+
scoped-delete, спец-поля; повторный прогон — идемпотентность.
|
|
122
|
+
3. `run_forever(interval=…)`: цикл чистит состояние, notify только после успешного save, реакция на SIGTERM.
|
|
123
|
+
4. Docker: `docker compose up` с заполненным `.env` → данные грузятся без правок кода; `docker stop` штатно завершает.
|
|
124
|
+
5. `uv build` + установка из wheel в чистом окружении (3.10): импорт и `cdc-1c`/`python -m cdc_1c`.
|