compare-schema 0.1.1__py3-none-any.whl

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.
@@ -0,0 +1,206 @@
1
+ Metadata-Version: 2.4
2
+ Name: compare-schema
3
+ Version: 0.1.1
4
+ Summary: Compare schema and data between databases
5
+ Author-email: Gu Park <doctorgu@kakao.com>
6
+ License-Expression: MIT
7
+ Project-URL: homepage, https://github.com/doctorgu/compare_schema
8
+ Project-URL: repository, https://github.com/doctorgu/compare_schema
9
+ Project-URL: documentation, https://github.com/doctorgu/compare_schema
10
+ Keywords: compare schema,database comparison,schema diff,mysql,sqlite
11
+ Requires-Python: >=3.13
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: sqlite3-client>=1.2.0
15
+ Requires-Dist: mysqlclient-client>=1.2.0
16
+ Requires-Dist: pydantic>=2.0
17
+ Requires-Dist: pydantic-settings>=2.0
18
+ Requires-Dist: xlsxwriter>=3.0
19
+ Requires-Dist: tabulate>=0.9
20
+ Requires-Dist: types-tabulate>=0.10
21
+ Requires-Dist: requests>=2.28
22
+ Requires-Dist: PyYAML>=6.0
23
+ Requires-Dist: SQLAlchemy>=2.0
24
+ Requires-Dist: PyMySQL>=1.0
25
+ Requires-Dist: python-dotenv>=1.0.0
26
+ Requires-Dist: ruff
27
+ Provides-Extra: test
28
+ Requires-Dist: pytest; extra == "test"
29
+ Requires-Dist: pytest-cov; extra == "test"
30
+ Requires-Dist: pytest-env; extra == "test"
31
+ Requires-Dist: pytest-mock; extra == "test"
32
+ Requires-Dist: pytest-asyncio; extra == "test"
33
+ Requires-Dist: build; extra == "test"
34
+ Dynamic: license-file
35
+
36
+ # compare-schema — Database Schema and Data Comparison Tool
37
+
38
+ [English](README.md) | [한국어](README_KO.md)
39
+
40
+ A database schema and data comparison tool across different environments (e.g. dev, stg, prd).
41
+
42
+ > [!NOTE]
43
+ > Currently, only **MySQL** schema and data are supported. The comparison metadata and version history are stored locally in an embedded SQLite database.
44
+
45
+ ## Features
46
+
47
+ - **Schema Comparison**: Compare tables, views, columns, indices, and foreign key references across databases.
48
+ - **Data Comparison**: Compare key-based data records across environments.
49
+ - **Database History Tracking**: Track changes to the same database over time by comparing versions.
50
+ - **Multiple Output Formats**: Export comparison reports to Markdown (`.md`) or Excel (`.xlsx`).
51
+ - **History & Versioning**: Tracks versions in SQLite to detect schema drift over time.
52
+ - **Notifications**: Discord webhook notification support for schema and data changes.
53
+ - **CLI & Module Support**: Run via `compare-schema` CLI command or `python -m src.main`.
54
+
55
+ ## Installation
56
+
57
+ ```bash
58
+ pip install compare-schema
59
+ ```
60
+
61
+ ## Quick Start
62
+
63
+ ### 1. Configuration (`config/config.yaml`)
64
+
65
+ Create a configuration YAML file:
66
+
67
+ ```yaml
68
+ output_type: md # md or xlsx
69
+ output_dir: ./compare_schema/result
70
+ prev_version: ""
71
+
72
+ data_tables:
73
+ - table: users
74
+ columns:
75
+ - name
76
+ - email
77
+ - nick_name
78
+ key_columns:
79
+ - id
80
+
81
+ exclude_tables: []
82
+ exclude_columns: []
83
+ db_host: 127.0.0.1
84
+ envs:
85
+ - name: dev
86
+ port: 3306
87
+ db_names: [shop, logs]
88
+ - name: stg
89
+ port: 3306
90
+ db_names: [shop]
91
+ - name: prd
92
+ port: 3306
93
+ db_names: [shop]
94
+
95
+ compare_list:
96
+ # Compare between different databases or environments
97
+ - a: dev.shop
98
+ b: dev.logs
99
+ - a: dev.shop
100
+ b: stg.shop
101
+ - a: stg.shop
102
+ b: prd.shop
103
+ # If a and b is same, compares between recent version and new version (shows history of that DB)
104
+ - a: dev.shop
105
+ b: dev.shop
106
+ - a: stg.shop
107
+ b: stg.shop
108
+ - a: prd.shop
109
+ b: prd.shop
110
+ ```
111
+
112
+ ### 2. Version Comparison & History
113
+
114
+ The tool supports two comparison modes based on the entries defined in `compare_list` (`{env}.{database}`):
115
+
116
+ #### A. Cross-Database Comparison (`a != b`)
117
+
118
+ Compares schemas and data between two distinct environments or databases (e.g., `dev.shop` vs `stg.shop`):
119
+
120
+ - **Schema Differences**:
121
+ - `schema_not_exists`: Identifies missing tables, views, columns, indices, and foreign keys present in one database but absent in the other.
122
+ - `schema_diff`: Identifies structural discrepancies in matching objects:
123
+ - **Columns**: Data types, nullability (`is_nullable`), default values, character length, numeric precision/scale, and column comments.
124
+ - **Indices**: Index types (BTREE, etc.), uniqueness (`is_unique`), and indexed column combinations.
125
+ - **Foreign Keys**: Target referenced tables and columns.
126
+ - **Data Differences**:
127
+ - Compares rows in tables specified in `data_tables`.
128
+ - Matches rows between databases using the primary/unique keys configured in `key_columns`.
129
+ - `data_not_exists`: Rows present in one database but missing in the other.
130
+ - `data_diff`: Rows that exist in both databases but have differing values in the monitored `columns`.
131
+ - **Output**: Exported to `{version}_compare.md` (Markdown) or the `compare` sheets in Excel.
132
+
133
+ #### B. Database History & Drift Tracking (`a == b`)
134
+
135
+ When `a` and `b` reference the same database (e.g., `a: dev.shop, b: dev.shop`), the tool compares the new `version` (timestamped as `YYYYMMDDHHmm`) against `prev_version`:
136
+
137
+ - **Change Categories**:
138
+ - `schema_added` / `data_added`: Newly created tables, views, columns, indices, foreign keys, or inserted rows.
139
+ - `schema_removed` / `data_removed`: Dropped schema objects or deleted rows.
140
+ - `schema_changed` / `data_changed`: Modified column types/properties or updated data row values over time.
141
+ - **Output**: Exported to `{version}_log.md` (Markdown) or the `log` sheets in Excel.
142
+
143
+ ---
144
+
145
+ ### Configuration Reference (`config.yaml`)
146
+
147
+ | Parameter | Type | Default | Description |
148
+ | :--- | :--- | :--- | :--- |
149
+ | `output_type` | `Literal["xlsx", "md"]` | Required | Output format. `"md"` generates Markdown files; `"xlsx"` generates an Excel workbook. |
150
+ | `output_dir` | `str` | Required | Directory path where output reports are saved. |
151
+ | `prev_version` | `str` | `""` | Previous version (`YYYYMMDDHHmm`) to compare against. If empty (`""`), automatically uses the latest version recorded in SQLite. |
152
+ | `process_when_changed` | `bool` | Required | If `true`, stops execution without generating reports if no schema or data changes are detected against `prev_version`. If `false`, generates reports on every run. |
153
+ | `data_tables` | `list[DataTableConfig]` | `[]` | List of tables and columns to compare data for. |
154
+ | `data_tables[].table` | `str` | Required | Table name in MySQL. |
155
+ | `data_tables[].columns` | `list[str]` | Required | Columns to compare for value differences. |
156
+ | `data_tables[].key_columns` | `list[str]` | Required | Key columns (e.g., primary key) used to match corresponding rows across databases. |
157
+ | `exclude_tables` | `list[str]` | Required | Table names to exclude from cross-environment schema comparison (`a != b`), such as migration tables (e.g., `["alembic_version"]`). *(Not excluded during same-database history tracking `a == b`)*. |
158
+ | `exclude_columns` | `list[str]` | Required | Column names to ignore during cross-environment column comparisons. |
159
+ | `db_host` | `str` | Required | Hostname or IP address for MySQL database connections. |
160
+ | `envs` | `list[EnvConfig]` | Required | List of environments and databases to connect to and extract. |
161
+ | `envs[].name` | `str` | Required | Environment identifier (e.g., `dev`, `stg`, `prd`). |
162
+ | `envs[].port` | `int` | Required | MySQL port number for this environment. |
163
+ | `envs[].db_names` | `list[str]` | Required | List of database names to extract from this environment. |
164
+ | `compare_list` | `list[EnvCompareConfig]` | Required | Target database pairs to compare (e.g., `a: dev.shop`, `b: stg.shop`). |
165
+
166
+ ---
167
+
168
+ ### How It Works
169
+
170
+ 1. **Extraction & Snapshotting**:
171
+ - Connects to MySQL instances defined in `envs` using credentials from `.env`.
172
+ - Extracts schema definitions (`information_schema.TABLES`, `COLUMNS`, `STATISTICS`, `KEY_COLUMN_USAGE`) and records from `data_tables`.
173
+ - Saves a point-in-time snapshot into an embedded SQLite database (`db_client/schema_data.sqlite3`), tagged with the current version timestamp (`YYYYMMDDHHmm`).
174
+ 2. **Change Detection**:
175
+ - Queries SQLite to check whether any schema or data differences exist between the current version and `prev_version`.
176
+ - If `process_when_changed: true` and no changes are found, execution halts early.
177
+ 3. **Comparison Execution**:
178
+ - Compares all configured pairs in `compare_list` using parameterized queries with `#foreach` list filtering.
179
+ 4. **Report Export & Alerts**:
180
+ - Writes Markdown or Excel reports to `data/`.
181
+ - If schema or data changes are detected in log mode (`is_log: true`), an alert is automatically posted to Discord via `DISCORD_WEB_HOOK_URL`.
182
+ - Any runtime errors or exceptions are formatted and sent to Discord as alerts.
183
+
184
+ ### 3. Environment Variables (`.env`)
185
+
186
+ Set database credentials in `.env`:
187
+
188
+ ```env
189
+ DB_USERNAME=your_username
190
+ DB_PASSWORD=your_password
191
+ DISCORD_WEB_HOOK_URL=https://discord.com/api/webhooks/...
192
+ ```
193
+
194
+ ### 4. Run Comparison
195
+
196
+ ```bash
197
+ # Run via CLI
198
+ compare-schema --config_path config/config.yaml
199
+
200
+ # Or run as Python module
201
+ python -m src.main --config_path config/config.yaml
202
+ ```
203
+
204
+ ## License
205
+
206
+ MIT License. See [LICENSE](LICENSE) for details.
@@ -0,0 +1,24 @@
1
+ compare_schema-0.1.1.dist-info/licenses/LICENSE,sha256=SceEQXH2EqmhxRLvy-52rf03gwxH8LrGttgH-lannvU,1065
2
+ db_client/schema.sql,sha256=L8c87TmnsCkpQp2uKW4x6nn1hTJ4bs5ONYuhiqa5yPY,1804
3
+ db_client/schema_client.py,sha256=QI3w8pFuyYich_J4uy0YwHnj6h-E2idHjjNEb_IwKUs,285
4
+ db_client/schema_settings.py,sha256=aGmfLtEVcAg3mrwHbB_GCvaJzL2JdC_FBvv0QKXMmto,1122
5
+ db_client/source_client.py,sha256=y2EP4s_1qKCRPac_VcBJ5lzg8k0eSpOjELklIQ3gXiU,505
6
+ db_client/source_settings.py,sha256=NR5XHBteLOUJFVahCs3fhNoldVVAJLOhen9chh_gwYo,1240
7
+ db_client/queries/schema/data.yml,sha256=1izUdJpcC7kamR3Y9fpRM4WyXSe-ekqjpFLJqTozpwA,3848
8
+ db_client/queries/schema/schema.yml,sha256=WtB__WOEZ--lLeVygrhME1Bmc9V5iyTDMr3sjXEmXNU,20524
9
+ db_client/queries/source/import_data.yml,sha256=hFcwwZ0Fm_4VY9Lwcl9o-ewe3-SbriRXhpeJDEB6gOc,188
10
+ db_client/queries/source/import_schema.yml,sha256=tKONtDwTx4Wpqt1oW93OJEr1J6-ErEqYky182f-99ew,2185
11
+ src/config.py,sha256=_xfR1G8C9rzzvTwd8C0nszNL9Qx34j58dzmoCjbw6t8,1392
12
+ src/env_config.py,sha256=-JeN-P_5X4J3r02_mrLCnbRlY5lcmYUEf8NUUue_Gio,385
13
+ src/excel_helper.py,sha256=6zlzvUFggEznnWiIq0vA4P_nweBO7I7v2R1ZL_cGX2E,2452
14
+ src/main.py,sha256=uO0fSNnW6c9dqksX3UqszTkyakcf77Wus7xS4JVU5iw,47555
15
+ src/markdown_helper.py,sha256=cMyF0FSm0tu3PqfJ2joerrZPqouX_yVgUdbUOSppWXY,2624
16
+ src/models.py,sha256=trzluwMu1gaCL_drEprGcDRiGVf0ftoeGNl5jGMgdi0,6774
17
+ src/util_mysql.py,sha256=lknFYsM57d42-xY0LMG4UzV41bG1vFhRT8dkLdhWMxk,10781
18
+ src/util_other.py,sha256=qMxTLqwpHLR5v90KZEYlvLh2qrsyxQbMklZjOmBHXJk,624
19
+ src/util_path.py,sha256=6FBCRFjZPPFOxmh1SQXn4g8bGN-nQKvrrZua5-nry6U,3505
20
+ compare_schema-0.1.1.dist-info/METADATA,sha256=fInsqSoxpttNg9UABnmWdsoU9G8FT8S1mCXJtFqV_00,8849
21
+ compare_schema-0.1.1.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
22
+ compare_schema-0.1.1.dist-info/entry_points.txt,sha256=A5fUzIs5Z1S51jWFufwMNoYLf1U8xPDiY-ShEdcZXUA,49
23
+ compare_schema-0.1.1.dist-info/top_level.txt,sha256=nwhb5mKiVo0RuDBMfpvgFk3OC5T4z6FMIOq-Z2FD_is,14
24
+ compare_schema-0.1.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ compare-schema = src.main:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 doctorgu
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.
@@ -0,0 +1,2 @@
1
+ db_client
2
+ src
@@ -0,0 +1,142 @@
1
+ - name: delete_data_by_version
2
+ value: |
3
+ DELETE
4
+ FROM ${table}
5
+ WHERE version = :version
6
+
7
+ - name: insert_data_from_mysql
8
+ value: |
9
+ INSERT INTO ${table}
10
+ (
11
+ version, env_name, db_name,
12
+ ${columns}
13
+ )
14
+ VALUES
15
+ (
16
+ :version, :env_name, :db_name,
17
+ ${values}
18
+ )
19
+
20
+ - name: read_table_sql
21
+ value: |
22
+ SELECT sql
23
+ FROM sqlite_master
24
+ WHERE type = 'table'
25
+ AND name = :table
26
+
27
+ - name: read_table_info
28
+ value: |
29
+ PRAGMA table_info("${table}")
30
+
31
+ - name: create_backup_table
32
+ value: |
33
+ DROP TABLE IF EXISTS "${backup_table}";
34
+
35
+ CREATE TABLE "${backup_table}" AS
36
+ SELECT *
37
+ FROM "${table}";
38
+
39
+ DROP TABLE "${table}";
40
+
41
+ - name: create_table_index
42
+ value: |
43
+ CREATE TABLE "${table}" (
44
+ ${columns}
45
+ ) STRICT;
46
+
47
+ CREATE UNIQUE INDEX IF NOT EXISTS "${table}_unique_idx" ON "${table}" (${index_cols});
48
+
49
+ - name: read_data_not_exists
50
+ value: |
51
+ SELECT a.env_name, a.db_name,
52
+ a.${key_columns}
53
+ FROM ${table} a
54
+ LEFT JOIN ${table} b
55
+ ON
56
+ #if ${other_version}
57
+ b.version = :other_version
58
+ #else
59
+ a.version = b.version
60
+ #endif
61
+ AND b.env_name = :other_env_name
62
+ AND b.db_name = :other_db_name
63
+ AND ${key_columns_equal}
64
+ WHERE a.version = :current_version
65
+ AND a.env_name = :current_env_name
66
+ AND a.db_name = :current_db_name
67
+ AND b.version IS NULL
68
+
69
+ - name: read_data_different
70
+ value: |
71
+ SELECT a.${key_columns},
72
+ ${other_columns_comma_a},
73
+ ${other_columns_comma_b},
74
+ a.env_name, a.db_name
75
+ FROM ${table} a
76
+ INNER JOIN ${table} b
77
+ ON
78
+ #if ${other_version}
79
+ b.version = :other_version
80
+ #else
81
+ a.version = b.version
82
+ #endif
83
+ AND b.env_name = :other_env_name
84
+ AND b.db_name = :other_db_name
85
+ AND ${key_columns_equal}
86
+ WHERE a.version = :current_version
87
+ AND a.env_name = :current_env_name
88
+ AND a.db_name = :current_db_name
89
+ AND
90
+ (
91
+ 1 != 1
92
+ ${other_columns_inequal}
93
+ )
94
+
95
+ - name: read_not_same_data
96
+ value: |
97
+ WITH
98
+ #foreach dt in ${data_tables} separator=','
99
+ n_${dt.table} AS (
100
+ SELECT env_name, db_name,
101
+ #foreach col in ${dt.columns} separator=','
102
+ "${col}"
103
+ #endfor
104
+ FROM "${dt.table}"
105
+ WHERE version = :version
106
+ )
107
+ , o_${dt.table} AS (
108
+ SELECT env_name, db_name,
109
+ #foreach col in ${dt.columns} separator=','
110
+ "${col}"
111
+ #endfor
112
+ FROM "${dt.table}"
113
+ WHERE version = :prev_version
114
+ )
115
+ #endfor
116
+ , cnt AS (
117
+ #foreach idx, dt in ${data_tables} separator=' UNION ALL '
118
+ SELECT '${dt.table}' compare_type,
119
+ (
120
+ SELECT count(*) cnt_n
121
+ FROM n_${dt.table}
122
+ ) n,
123
+ (
124
+ SELECT count(*) cnt_o
125
+ FROM o_${dt.table}
126
+ ) o,
127
+ (
128
+ SELECT count(*) cnt_n_o
129
+ FROM n_${dt.table} n
130
+ INNER JOIN o_${dt.table} o
131
+ ON o.env_name = n.env_name
132
+ AND o.db_name = n.db_name
133
+ AND
134
+ #foreach col in ${dt.columns} separator=' AND '
135
+ COALESCE(o."${col}", '<NULL>') = COALESCE(n."${col}", '<NULL>')
136
+ #endfor
137
+ ) n_o
138
+ #endfor
139
+ )
140
+ SELECT compare_type
141
+ FROM cnt
142
+ WHERE n != o OR o != n_o