ezr-database-sdk 0.1.2__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.
Files changed (24) hide show
  1. ezr_database_sdk-0.1.2/MANIFEST.in +5 -0
  2. ezr_database_sdk-0.1.2/PKG-INFO +76 -0
  3. ezr_database_sdk-0.1.2/README.md +64 -0
  4. ezr_database_sdk-0.1.2/database.example.yaml +20 -0
  5. ezr_database_sdk-0.1.2/docs/database-sdk.md +143 -0
  6. ezr_database_sdk-0.1.2/docs/plans/2026-10-26-SDK/345/217/221/345/270/203/346/265/201/347/250/213.md +26 -0
  7. ezr_database_sdk-0.1.2/docs/plans/2026-10-26-/346/225/260/346/215/256/345/272/223/345/205/203/346/225/260/346/215/256SDK.md +40 -0
  8. ezr_database_sdk-0.1.2/docs/plans/2026-10-26-/346/230/276/345/274/217/345/210/206/350/241/250/347/256/227/346/263/225/346/216/245/345/217/243.md +20 -0
  9. ezr_database_sdk-0.1.2/docs/releasing.md +91 -0
  10. ezr_database_sdk-0.1.2/ezr_database_sdk/__init__.py +16 -0
  11. ezr_database_sdk-0.1.2/ezr_database_sdk/client.py +180 -0
  12. ezr_database_sdk-0.1.2/ezr_database_sdk/config.py +75 -0
  13. ezr_database_sdk-0.1.2/ezr_database_sdk/errors.py +21 -0
  14. ezr_database_sdk-0.1.2/ezr_database_sdk/routing.py +61 -0
  15. ezr_database_sdk-0.1.2/ezr_database_sdk.egg-info/PKG-INFO +76 -0
  16. ezr_database_sdk-0.1.2/ezr_database_sdk.egg-info/SOURCES.txt +22 -0
  17. ezr_database_sdk-0.1.2/ezr_database_sdk.egg-info/dependency_links.txt +1 -0
  18. ezr_database_sdk-0.1.2/ezr_database_sdk.egg-info/requires.txt +6 -0
  19. ezr_database_sdk-0.1.2/ezr_database_sdk.egg-info/top_level.txt +1 -0
  20. ezr_database_sdk-0.1.2/pyproject.toml +19 -0
  21. ezr_database_sdk-0.1.2/setup.cfg +4 -0
  22. ezr_database_sdk-0.1.2/tests/test_database_sdk.py +315 -0
  23. ezr_database_sdk-0.1.2/tests/test_upload.py +174 -0
  24. ezr_database_sdk-0.1.2/upload.py +144 -0
@@ -0,0 +1,5 @@
1
+ include upload.py
2
+ include database.example.yaml
3
+ recursive-include docs *.md
4
+ recursive-include tests test_*.py
5
+ prune release-artifacts
@@ -0,0 +1,76 @@
1
+ Metadata-Version: 2.4
2
+ Name: ezr-database-sdk
3
+ Version: 0.1.2
4
+ Summary: Synchronous read-only EZR brand database metadata and table routing SDK
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: PyMySQL>=1.0.0
8
+ Requires-Dist: PyYAML>=6.0
9
+ Provides-Extra: release
10
+ Requires-Dist: build>=1.2; extra == "release"
11
+ Requires-Dist: twine>=5.0; extra == "release"
12
+
13
+ # EZR Database SDK
14
+
15
+ 公司内部数据库元数据 SDK,提供同步、只读的 Python 接口:
16
+
17
+ - 多环境基础库配置,实例间连接隔离。
18
+ - 查询品牌的业务库连接节点和分表配置。
19
+ - 根据整数或字符串 ID 计算物理表名,兼容 C# BKDR Hash。
20
+
21
+ Python 3.10+,包名 `ezr-database-sdk`,导入名 `ezr_database_sdk`。
22
+
23
+ ## 安装与开发
24
+
25
+ ```bash
26
+ python -m venv .venv
27
+ .venv/bin/python -m pip install -e .
28
+ .venv/bin/python -m unittest discover -s tests -v
29
+ ```
30
+
31
+ 其他项目在自己的虚拟环境内安装本项目路径或已发布的 wheel:
32
+
33
+ ```bash
34
+ python -m pip install /path/to/ezr-database-sdk
35
+ ```
36
+
37
+ ## 快速使用
38
+
39
+ 复制 `database.example.yaml` 为私有的 `database.yaml`,设置选中环境的配置变量:
40
+
41
+ ```python
42
+ from ezr_database_sdk import (
43
+ EzrDatabaseSDK, physical_table_name_by_int, physical_table_name_by_hash,
44
+ )
45
+
46
+ with EzrDatabaseSDK.from_yaml("database.yaml", env="qcloud") as sdk:
47
+ rules = sdk.get_table_shards(brand_id=6812, database="crm")
48
+ nodes = sdk.get_connection_strings(brand_id=6812, database="crm")
49
+ # 计算已配置分表规则的表名:
50
+ # table = sdk.get_physical_table_name_by_int(6812, "crm", "实际逻辑表名", 123)
51
+ # hashed = sdk.get_physical_table_name_by_hash(6812, "crm", "实际逻辑表名", "123")
52
+
53
+ # 不连接数据库的纯计算接口:
54
+ assert physical_table_name_by_int("vip", 123, 16) == "vip12"
55
+ assert physical_table_name_by_hash("vip", "123", 16) == "vip3"
56
+ ```
57
+
58
+ 两种算法通过方法名显式选择,并严格检查参数类型,不自动转换;整数方法传入字符串、Hash 方法传入整数均会报错。旧的自动分派接口仅保留兼容并标记弃用,迁移说明见使用文档。
59
+
60
+ 连接串含敏感凭据,禁止打印或写入日志。建议每个线程独立创建 SDK 实例,并使用上下文管理器关闭。
61
+
62
+ 详细配置、返回字段、业务库代号、算法兼容及错误处理见 [使用文档](docs/database-sdk.md)。
63
+
64
+ ## 发布
65
+
66
+ ```bash
67
+ .venv/bin/python -m pip install -e '.[release]'
68
+ .venv/bin/python upload.py --dry-run # 预演,不修改、不上传
69
+ .venv/bin/python upload.py --build-only # 测试、构建、校验,不改版本、不上传
70
+ # 正式发布(先配置 Twine 凭据;会递增版本并实际上传):
71
+ # .venv/bin/python upload.py
72
+ ```
73
+
74
+ 版本递增规则与 `kxy/framework` 一致,补丁号超过 19 时进位。仓库、凭据和故障恢复见 [发布文档](docs/releasing.md)。
75
+
76
+ 本项目从 `testdata` 工具项目独立迁出,不依赖其脚本、私有配置或数据库凭据。
@@ -0,0 +1,64 @@
1
+ # EZR Database SDK
2
+
3
+ 公司内部数据库元数据 SDK,提供同步、只读的 Python 接口:
4
+
5
+ - 多环境基础库配置,实例间连接隔离。
6
+ - 查询品牌的业务库连接节点和分表配置。
7
+ - 根据整数或字符串 ID 计算物理表名,兼容 C# BKDR Hash。
8
+
9
+ Python 3.10+,包名 `ezr-database-sdk`,导入名 `ezr_database_sdk`。
10
+
11
+ ## 安装与开发
12
+
13
+ ```bash
14
+ python -m venv .venv
15
+ .venv/bin/python -m pip install -e .
16
+ .venv/bin/python -m unittest discover -s tests -v
17
+ ```
18
+
19
+ 其他项目在自己的虚拟环境内安装本项目路径或已发布的 wheel:
20
+
21
+ ```bash
22
+ python -m pip install /path/to/ezr-database-sdk
23
+ ```
24
+
25
+ ## 快速使用
26
+
27
+ 复制 `database.example.yaml` 为私有的 `database.yaml`,设置选中环境的配置变量:
28
+
29
+ ```python
30
+ from ezr_database_sdk import (
31
+ EzrDatabaseSDK, physical_table_name_by_int, physical_table_name_by_hash,
32
+ )
33
+
34
+ with EzrDatabaseSDK.from_yaml("database.yaml", env="qcloud") as sdk:
35
+ rules = sdk.get_table_shards(brand_id=6812, database="crm")
36
+ nodes = sdk.get_connection_strings(brand_id=6812, database="crm")
37
+ # 计算已配置分表规则的表名:
38
+ # table = sdk.get_physical_table_name_by_int(6812, "crm", "实际逻辑表名", 123)
39
+ # hashed = sdk.get_physical_table_name_by_hash(6812, "crm", "实际逻辑表名", "123")
40
+
41
+ # 不连接数据库的纯计算接口:
42
+ assert physical_table_name_by_int("vip", 123, 16) == "vip12"
43
+ assert physical_table_name_by_hash("vip", "123", 16) == "vip3"
44
+ ```
45
+
46
+ 两种算法通过方法名显式选择,并严格检查参数类型,不自动转换;整数方法传入字符串、Hash 方法传入整数均会报错。旧的自动分派接口仅保留兼容并标记弃用,迁移说明见使用文档。
47
+
48
+ 连接串含敏感凭据,禁止打印或写入日志。建议每个线程独立创建 SDK 实例,并使用上下文管理器关闭。
49
+
50
+ 详细配置、返回字段、业务库代号、算法兼容及错误处理见 [使用文档](docs/database-sdk.md)。
51
+
52
+ ## 发布
53
+
54
+ ```bash
55
+ .venv/bin/python -m pip install -e '.[release]'
56
+ .venv/bin/python upload.py --dry-run # 预演,不修改、不上传
57
+ .venv/bin/python upload.py --build-only # 测试、构建、校验,不改版本、不上传
58
+ # 正式发布(先配置 Twine 凭据;会递增版本并实际上传):
59
+ # .venv/bin/python upload.py
60
+ ```
61
+
62
+ 版本递增规则与 `kxy/framework` 一致,补丁号超过 19 时进位。仓库、凭据和故障恢复见 [发布文档](docs/releasing.md)。
63
+
64
+ 本项目从 `testdata` 工具项目独立迁出,不依赖其脚本、私有配置或数据库凭据。
@@ -0,0 +1,20 @@
1
+ # Copy to a private config file. Never commit real credentials.
2
+ # Use a MySQL account with SELECT permissions on the metadata tables only.
3
+ environments:
4
+ qcloud:
5
+ host: ${EZR_QCLOUD_DB_HOST}
6
+ port: 3306
7
+ user: ${EZR_QCLOUD_DB_USER}
8
+ password: ${EZR_QCLOUD_DB_PASSWORD}
9
+ base_database: ezp-base
10
+ brand_database: ezp-ed
11
+ connect_timeout: 10
12
+ read_timeout: 30
13
+ write_timeout: 30
14
+ q1cloud:
15
+ host: ${EZR_Q1CLOUD_DB_HOST}
16
+ port: 3306
17
+ user: ${EZR_Q1CLOUD_DB_USER}
18
+ password: ${EZR_Q1CLOUD_DB_PASSWORD}
19
+ base_database: ezp-base
20
+ brand_database: ezp-ed
@@ -0,0 +1,143 @@
1
+ # EZR 数据库元数据 SDK
2
+
3
+ 同步、独立可安装、只读查询品牌分库/分表元数据并计算物理表名。不依赖本仓库的复制和清理脚本,不执行业务库 SQL,不修改数据库配置。
4
+
5
+ ## 安装
6
+
7
+ Python 3.10+,在使用方项目的独立虚拟环境中安装本项目或发布后的 wheel:
8
+
9
+ ```bash
10
+ python -m venv .venv
11
+ .venv/bin/python -m pip install /path/to/ezr-database-sdk
12
+ # 开发当前 SDK
13
+ .venv/bin/python -m pip install -e .
14
+ .venv/bin/python -m unittest discover -s tests -v
15
+ ```
16
+
17
+ 包名:`ezr-database-sdk`,导入名:`ezr_database_sdk`。
18
+
19
+ ## 配置与环境隔离
20
+
21
+ 参见 `database.example.yaml`。每个实例必须显式指定 `env`,没有默认/回退环境。每个实例独立连接,按首次查询懒加载,不使用进程全局连接或缓存。
22
+
23
+ ```yaml
24
+ environments:
25
+ qcloud:
26
+ host: ${EZR_QCLOUD_DB_HOST}
27
+ port: 3306
28
+ user: ${EZR_QCLOUD_DB_USER}
29
+ password: ${EZR_QCLOUD_DB_PASSWORD}
30
+ base_database: ezp-base
31
+ brand_database: ezp-ed
32
+ ```
33
+
34
+ 必填 `host`、`user`、`password`;端口默认 3306;超时单位秒,`connect_timeout=10`、`read_timeout=30`、`write_timeout=30`。端口和超时必须为 YAML 整数。`${ENV_VAR}` 在选中环境的字符串字段中替换一次,缺失即报错;未选中的环境不要求提供变量。变量替换结果不递归解析。
35
+
36
+ 来源项目 `testdata` 的配置以 source/target 为导向,因此 SDK 使用独立配置,不猜测或复用旧配置里的环境对应关系。`ezp-ed` 与 `ezp-base` 需在同一已配置 MySQL 服务上可访问,这与来源项目的脚本一致。
37
+
38
+ 也可以直接传入字典:
39
+
40
+ ```python
41
+ import os
42
+ from ezr_database_sdk import EzrDatabaseSDK
43
+
44
+ sdk = EzrDatabaseSDK({"environments": {"qcloud": {
45
+ "host": os.environ["EZR_QCLOUD_DB_HOST"],
46
+ "user": os.environ["EZR_QCLOUD_DB_USER"],
47
+ "password": os.environ["EZR_QCLOUD_DB_PASSWORD"],
48
+ }}}, env="qcloud")
49
+ sdk.close()
50
+ ```
51
+
52
+ ## 使用
53
+
54
+ ```python
55
+ from ezr_database_sdk import (
56
+ EzrDatabaseSDK, get_code, physical_table_name_by_int, physical_table_name_by_hash,
57
+ )
58
+
59
+ with EzrDatabaseSDK.from_yaml("database.yaml", env="qcloud") as sdk:
60
+ # 查询指定业务库下的全部分表配置;返回 list[dict],保留原始字段。
61
+ rules = sdk.get_table_shards(brand_id=6812, database="crm")
62
+
63
+ # 按逻辑表名筛选。
64
+ rules = sdk.get_table_shards(6812, "crm", table_code="crm_vip_info")
65
+
66
+ # 返回全部主/从节点,不自动选主或按 DbIndex 推断数据库路由。
67
+ nodes = sdk.get_connection_strings(6812, "crm")
68
+ for node in nodes:
69
+ is_master = node["IsMaster"] # 数据库原值,例如 "1" / "0"
70
+ db_index = node["DbIndex"]
71
+ connection_string = node["DbConnString"] # 含凭据,禁止记录日志!
72
+
73
+ # 示例表必须存在分表配置,否则明确抛错。
74
+ table = sdk.get_physical_table_name_by_int(
75
+ brand_id=6812, database="crm", table_code="crm_vip_info", shard_key=123,
76
+ )
77
+ table_from_string = sdk.get_physical_table_name_by_hash(6812, "crm", "crm_vip_info", "123")
78
+
79
+ # 纯算法接口无需连接数据库,同样显式选择算法:
80
+ assert get_code("123") == 847490
81
+ assert physical_table_name_by_int("vip", 123, 16) == "vip12"
82
+ assert physical_table_name_by_hash("vip", "123", 16) == "vip3"
83
+ ```
84
+
85
+ 一个实例不能跨线程并发使用。建议每个线程/任务独立创建实例并使用上下文管理;`close()` 可重复调用,但关闭后不能复用。SDK 不自动重试/重连,不缓存元数据,网络失效后由调用方决定是否新建实例。
86
+
87
+ ## 元数据与返回值
88
+
89
+ 查询链路:
90
+
91
+ 1. `ezp-ed.ed_base_brand.Id = brand_id`,读取对应业务字段的分库 ID。
92
+ 2. 分表:`ezp-base.pf_sys_db_tableshard.ShardDbId = 分库 ID`。
93
+ 3. 连接:`ezp-base.pf_sys_db_shard_nodes.DbShardingId = 分库 ID`。
94
+
95
+ 无需读取 `pf_sys_db_shard` 即可通过已有外键得到规则与连接节点。
96
+
97
+ 支持的 `database`(不区分大小写)与品牌字段:
98
+
99
+ | 业务代号 | 品牌字段 |
100
+ | --- | --- |
101
+ | bd / crm / eop / mall | BdDbShardingId / CrmDbShardingId / EopDbShardingId / MallDbShardingId |
102
+ | order / cms / rtl / log | OrderDbShardingId / CmsDbShardingId / RtlDbShardingId / LogDbShardingId |
103
+ | prod / pay / ubs / oms | ProdDbShardingId / PayDbShardingId / UbsDbShardingId / OmsDbShardingId |
104
+ | dlake / bigdata | DLakeShardingId / BigDataShardingId |
105
+ | es / esproduct / esreport | ESDbShardingId / ESProdutShardingId / ESReportShardingId |
106
+
107
+ `ESProdutShardingId` 是数据库原始拼写,不是 SDK 拼写错误。代号是品牌业务字段映射,不直接使用 `pf_sys_db_shard.SystemCode`;没有品牌对应字段的业务不擅自推断。
108
+
109
+ 分表结果保留 `Id`、`CopId`、`ShardDbId`、`TableCode`、`TableCount` 等字段,节点保留 `Id`、`DbShardingId`、`IsMaster`、`DbIndex`、`DbConnString` 等字段。查询无匹配规则或节点返回空列表。
110
+
111
+ `CopId` 的业务语义尚未确认,查询返回全部匹配配置,不擅自按品牌过滤或定义优先级。计算表名时要求恰好一条规则,重复规则即使数量相同也报错。不把缺失规则当作“不分表”。
112
+
113
+ ## 算法兼容
114
+
115
+ 字符串采用提供的 C# `GetCode`:按 UTF-16 代码单元遍历,以 `hash = hash * 131 + char` 模拟 unchecked 32 位溢出,最终 `hash & 0x7FFFFFFF`。支持中文、非 BMP 字符与孤立代理字符,不执行 Unicode 归一化。不使用 Python 内置 `hash()` 或 .NET `string.GetHashCode()`。
116
+
117
+ ### 显式选择算法
118
+
119
+ | 用途 | 查询配置后计算的 SDK 方法 | 不访问数据库的纯函数 | shard_key 类型 |
120
+ | --- | --- | --- | --- |
121
+ | 整数直接取模 | `get_physical_table_name_by_int` | `physical_table_name_by_int` | 非负 `int`,不含 `bool` |
122
+ | BKDR Hash 后取模 | `get_physical_table_name_by_hash` | `physical_table_name_by_hash` | `str` |
123
+
124
+ 两个入口不根据参数类型切换算法,不进行隐式转换。例如,整数方法传入 `"123"`,或 Hash 方法传入 `123`,都会抛 `TypeError`;SDK 在访问数据库之前检查 key 类型。如果输入来自 HTTP/JSON 的字符串,但业务要求整数取模,调用方应先明确执行 `int(value)`,再传给整数方法。
125
+
126
+ 表名统一为 `基础表名 + str(值 % TableCount + 1)`,不额外添加下划线。Hash 方法的空字符串与整数方法的 0 均落入第 1 张表。拒绝 bool、浮点数、负整数 ID,以及非正整数分表数。负整数暂不支持,避免未经确认的 C#/Python 负余数差异。
127
+
128
+ ### 旧接口迁移
129
+
130
+ `physical_table_name` 和 `sdk.get_physical_table_name` 暂时保留原有按参数类型分派的行为以兼容旧调用,但均已弃用,调用时发出 `DeprecationWarning`(Python 默认可能不显示此类警告)。新代码必须使用上述显式方法;旧调用应按业务所需算法迁移,不能仅依赖当前变量的运行时类型决定使用哪个方法。
131
+
132
+ ## 错误与安全
133
+
134
+ - `ConfigurationError`:配置、环境或环境变量不正确。
135
+ - `MetadataNotFoundError`:品牌/业务分库未配置,或计算表名时缺少规则。
136
+ - `AmbiguousMetadataError`:计算表名时发现多条规则,无法安全选择。
137
+ - `DatabaseError`:连接、数据库读取或关闭失败;不透传可能包含凭据的驱动错误。
138
+ - `TypeError` / `ValueError`:调用参数不合法。
139
+ - `RuntimeError`:关闭后的实例被再次使用。
140
+
141
+ 业务参数均使用 SQL 绑定;配置中的数据库标识符经过白名单校验。请使用仅有元数据 SELECT 权限的专用账号(SDK 的只读方法不能替代数据库权限控制)。
142
+
143
+ 节点返回值 `ConnectionInfo` 是 dict 子类,默认 `repr`/`str` 隐藏 `DbConnString`;但显式读取、`dict(node)`、JSON 序列化仍包含真实凭据,不应打印或上报。不解析或改写连接串。实例配置的 repr 同样隐藏密码。
@@ -0,0 +1,26 @@
1
+ # SDK 发布流程实施计划
2
+
3
+ **目标:** 参考 `kxy/framework/upload.py`,为独立 SDK 实现版本递增、构建和 Twine 发布入口,不执行真实上传。
4
+
5
+ **架构:** 根目录 `upload.py` 读取 `[project].version`;使用当前 Python 解释器执行测试、PEP 517 构建、Twine 校验和上传。保留参考项目的 patch 超过 19 后 minor 加 1、patch 置 1 规则。凭据仅使用 Twine 的外部配置。
6
+
7
+ **技术栈:** Python 3.10+、标准库、build、twine、unittest。
8
+
9
+ ## 任务
10
+ 1. `tests/test_upload.py`:先覆盖普通递增/19进位、限定修改 project.version、预演无副作用、仅构建不改版本、不上传、测试/构建失败和上传失败恢复等,再实现。
11
+ 2. `upload.py`:默认发布时先运行测试,再递增版本、构建、校验并上传;`--dry-run` 零修改;`--build-only` 构建当前版本;`--no-bump` 用当前版本重试;可选 `--repository` 传递 Twine 仓库名。构建使用干净临时目录,最终产物存入 `release-artifacts/<version>/`,只上传本次产物,避免旧包混入。
12
+ 3. 构建/校验失败恢复版本;进入上传阶段后失败保留版本与产物,防止部分上传时回滚导致重复发布。没有自动提交或推送发布版本。
13
+ 4. `pyproject.toml`:增加 release 可选依赖;`.gitignore` 忽略产物/临时目录;`README.md` 和 `docs/releasing.md` 说明凭据、仓库、模式、故障恢复。
14
+ 5. 在 SDK 独立 `.venv` 安装 release 依赖,运行全部测试、dry-run、真实 build-only 和 Twine 校验。禁止执行真实上传;检查差异后提交,不 push。
15
+
16
+ ## 安全边界
17
+ - 参考项目仅只读,不复制其中的凭据、不修改该项目。
18
+ - 自动化测试 mock 所有上传命令;手动验证仅 dry-run / build-only。
19
+ - 不并发运行发布脚本;正式上传需要用户显式发起。
20
+
21
+ ## 验证结果
22
+ - 40 项 unittest 全部通过,包含版本进位、Windows 换行保留、CLI 参数、失败恢复和不上传保护。
23
+ - `--dry-run` 验证通过;`--build-only` 实际生成 `0.1.0` 源码包和 wheel,Twine strict 校验通过。
24
+ - 已核实 wheel 仅包含 SDK 包和分发元数据,源码包包含发布脚本、配置示例、文档和测试。
25
+ - 在独立验证虚拟环境安装 wheel,并使用隔离导入验证整数/字符串路由通过。
26
+ - `pip check` 和 git diff 空白检查通过;版本保持 `0.1.0`,没有执行真实上传或 push。
@@ -0,0 +1,40 @@
1
+ # 数据库元数据 SDK 实施计划
2
+
3
+ > 本文保留在 `testdata` 中初次实现时的设计与验证记录。经用户确认,SDK 已迁移至独立项目 `ezr-database-sdk`;运行代码与测试不变,新增独立 README、Git 仓库及虚拟环境。来源项目不再保留 SDK 文件,两侧分别提交,不 push。独立项目重新安装后 25 项测试全部通过。
4
+
5
+ **目标:** 在当前项目新增独立、同步、只读的 Python SDK,支持环境隔离、品牌分库/分表查询和兼容 C# 的物理表计算,不修改现有脚本。
6
+
7
+ **架构:** `ezr_database_sdk` 独立包;环境配置、纯路由算法、数据库读取分离。只连接配置指定的基础服务,通过 `ezp-ed.ed_base_brand` 的业务分库字段关联 `ezp-base` 元数据表,返回全部节点。连接由 SDK 持有、显式关闭或上下文管理;不依赖 KXY ORM,不修改其异步 DAL 约定。
8
+
9
+ **技术栈:** Python >=3.10、PyMySQL、PyYAML、unittest、setuptools。
10
+
11
+ ## 已确认
12
+ - 同步调用,整数 ID 直接取模,字符串 ID 按提供的 BKDR(seed=131)算法计算。
13
+ - C# UTF-16 字符、32 位溢出、最终 `& 0x7fffffff`。
14
+ - 表名为基础名直接拼接一基后缀,不附加下划线。
15
+ - 多环境实例独立,密码配置支持 `${ENV_VAR}`。
16
+ - 原始数据库字段已通过只读 SHOW COLUMNS 核实,分表字段为 `TableCode`、`TableCount`、`ShardDbId`、`CopId`。
17
+
18
+ ## 任务 1:纯路由与配置
19
+ 文件:`ezr_database_sdk/routing.py`、`config.py`、`errors.py`、`tests/test_database_sdk.py`。
20
+ 先编写 hash 固定向量、UTF-16、溢出、参数类型、非法分表数、环境变量和环境隔离测试;运行 unittest 验证失败,再实现。
21
+ 负整数暂拒绝,避免 Python/C# 负余数差异;bool 不视作 ID。表数量与 ID 均严格检查。
22
+
23
+ ## 任务 2:只读客户端
24
+ 文件:`ezr_database_sdk/client.py`、`__init__.py`。
25
+ 以 fake DB-API 连接测试品牌不存在、业务字段映射、全部节点返回、SQL 参数化、过滤和连接关闭,再实现。
26
+ 方法:`from_yaml`、`get_table_shards`、`get_connection_strings`、`get_physical_table_name`、`close`。
27
+ `get_table_shards` / `get_connection_strings` 保留数据库原始字段名称,连接串 repr 脱敏。
28
+ 分表规则不存在或重复均报明确异常,不推断 CopId 优先级,不回退默认环境、不自动挑选主库。无内部缓存,避免品牌/环境污染。
29
+
30
+ ## 任务 3:交付
31
+ 文件:`pyproject.toml`、`database.example.yaml`、`docs/database-sdk.md`。
32
+ 补充安装、配置、环境变量、上下文生命周期、数据结果、错误与线程安全说明。
33
+ 在项目 `.venv` 内安装依赖及 SDK,运行 `.venv/bin/python -m unittest discover -s tests -v`;只读真实元数据集成验证,不输出凭据。检查 diff,提交本次新增文件,不 push。
34
+
35
+ ## 完成与验证
36
+ - 通过 editable 安装,`pip check` 无依赖冲突。
37
+ - 25 项 unittest 通过;compileall 通过;git diff 空白检查通过。
38
+ - 已对当前配置的源基础库完成只读联调:品牌 CRM 下 6 条分表规则、2 个连接节点;整数、字符串和含非 BMP 字符的路由检查通过。未执行写操作,未打印连接串。
39
+ - 新增忽略 `/database.yaml`,防止误提交私有配置。
40
+ - 限制:CopId 未解释优先级;负整数 ID 明确拒绝;不自动推断 DbIndex 路由;未使用 C# 编译器交叉执行,仅通过固定向量与独立 Int32 溢出模型测试兼容性。
@@ -0,0 +1,20 @@
1
+ # 显式分表算法接口实施计划
2
+
3
+ **目标:** 根据用户确认的需求,把表名计算拆成整数取模与字符串 Hash 两个明确方法,避免由参数类型隐式选择算法。
4
+
5
+ **架构:** 纯算法层新增 `physical_table_name_by_int` / `physical_table_name_by_hash`;SDK 层新增 `get_physical_table_name_by_int` / `get_physical_table_name_by_hash`。两种方法共享元数据读取与基础校验,但分别严格接受非负 int(不含 bool)或 str,不自动类型转换。新 SDK 方法在读取数据库前检查 key 类型。
6
+
7
+ **兼容:** 保留 `physical_table_name` 和 `get_physical_table_name` 旧接口,行为不变并发出 DeprecationWarning;文档推荐并且示例只使用显式接口。BKDR、UTF-16、溢出及表名后缀规则不变。
8
+
9
+ ## 步骤
10
+ 1. 修改 `tests/test_database_sdk.py`,增加两套显式接口、跨类型拒绝、无数据库调用、缺失/重复配置、旧接口警告及返回值兼容测试;运行验证失败。
11
+ 2. 更新 `ezr_database_sdk/routing.py`、`client.py` 和 `__init__.py`,实现并导出新方法,保留旧兼容入口。
12
+ 3. 更新 `README.md`、`docs/database-sdk.md` 的示例与迁移说明。
13
+ 4. 使用 SDK `.venv` 运行所有测试、build-only / Twine 校验与 wheel 隔离安装验证;不改发布版本、不上传。检查差异,提交不 push。
14
+
15
+ ## 验证结果
16
+ - 47 项测试通过,开启 `DeprecationWarning` 作为错误时仍全部通过;旧接口测试明确验证警告。
17
+ - 新 SDK 入口在错误 key 类型时不访问数据库,两种算法覆盖缺失/重复配置和 UTF-16 字符。
18
+ - build-only 与 Twine strict 校验通过;wheel 独立环境安装后验证两种显式接口、Unicode 和错误类型拒绝通过。
19
+ - 验证期间检测到独立于本次修改的版本变化 `0.1.0 -> 0.1.1`;原样保留,不纳入本次提交。首次安装验证误用了旧版 wheel,改为根据当前版本选择产物后验证通过。
20
+ - 本次未执行实际上传,不 push。
@@ -0,0 +1,91 @@
1
+ # SDK 发布
2
+
3
+ 参考 `kxy/framework/upload.py` 的版本递增、源码包/wheel 构建、Twine 上传流程;适配本项目的 `pyproject.toml`,不再需要 `setup.py`。实际上传需要有目标仓库的发布权限。
4
+
5
+ ## 准备
6
+
7
+ 在本项目根目录执行,必须使用独立虚拟环境:
8
+
9
+ ```bash
10
+ python -m venv .venv
11
+ .venv/bin/python -m pip install -e '.[release]'
12
+ ```
13
+
14
+ 发布依赖 `build` 和 `twine` 属于可选依赖,不增加 SDK 使用方的基础运行依赖。构建默认使用 PEP 517 隔离环境,可能需要网络下载构建依赖。
15
+
16
+ ## 预演与本地验证
17
+
18
+ ```bash
19
+ # 仅展示版本与流程,不运行命令、不写文件、不上传。
20
+ .venv/bin/python upload.py --dry-run
21
+
22
+ # 运行全部测试,构建并校验当前版本;不递增版本,不上传。
23
+ .venv/bin/python upload.py --build-only
24
+ ```
25
+
26
+ 产物位于 `release-artifacts/<版本>/`。每次在项目内全新的临时目录构建,校验后只复制并使用本次生成的一个 `.tar.gz` 和一个 `.whl`,不会上传旧版本文件。临时目录在结束时自动清理,历史产物不自动删除。
27
+
28
+ ## 配置凭据
29
+
30
+ 推荐使用环境变量或用户级 `~/.pypirc`,禁止在脚本、项目配置、文档和 Git 中写入真实 token。
31
+
32
+ 以 PyPI token 为例,提前通过本机的安全方式注入:
33
+
34
+ ```bash
35
+ export TWINE_USERNAME=__token__
36
+ # 使用密码管理器/CI secret 设置 TWINE_PASSWORD;不要把真实 token 写入可提交的文件。
37
+ ```
38
+
39
+ 默认目标为 Twine 默认仓库 PyPI。支持 Twine 原生 `TWINE_REPOSITORY_URL`、`TWINE_REPOSITORY`、`TWINE_CONFIG_FILE` 等外部配置。私有仓库可在用户级 `.pypirc` 中配置,再用 `--repository internal` 指定名称。仓库 URL 中不要嵌入凭据。
40
+
41
+ 脚本使用 `--non-interactive`,缺少凭据时会失败,不会阻塞等待输入。Twine/构建工具的输出直接显示在终端,请勿将敏感环境变量打印到日志。
42
+
43
+ ## 正式发布
44
+
45
+ **以下命令会真的上传包,执行前务必确认仓库、包名和发布权限。**
46
+
47
+ ```bash
48
+ .venv/bin/python upload.py
49
+
50
+ # 使用用户级 .pypirc 中名为 internal 的仓库:
51
+ .venv/bin/python upload.py --repository internal
52
+
53
+ # TestPyPI(需单独配置对应 token):
54
+ .venv/bin/python upload.py --repository testpypi
55
+ ```
56
+
57
+ 流程:
58
+
59
+ 1. 读取 `[project].version`,只接受稳定的 `主.次.补丁` 数字版本。
60
+ 2. 运行 `unittest` 全部测试。测试失败时不改版本、不构建、不上传。
61
+ 3. 自动递增版本并更新 `pyproject.toml`,其他字段/注释不变。
62
+ 4. 使用当前解释器执行 `python -m build` 构建源码包和 wheel。
63
+ 5. 使用 `python -m twine check --strict` 校验元数据。
64
+ 6. 将本次产物保存到版本目录,再执行 Twine 上传。
65
+
66
+ 版本递增严格沿用参考项目规则:
67
+
68
+ | 当前版本 | 下次发布版本 |
69
+ | --- | --- |
70
+ | 0.1.0 | 0.1.1 |
71
+ | 1.4.18 | 1.4.19 |
72
+ | 1.4.19 | 1.5.1 |
73
+
74
+ 不生成日期版本;不使用 UTC 时间戳;不自动创建 Git tag、提交或 push。正式上传成功后,核对仓库并手动提交本次版本变化。为保持与参考项目一致,第一次默认发布也会从 `0.1.0` 递增到 `0.1.1`;如明确要发布已有版本,使用 `--no-bump`。
75
+
76
+ ## 失败与恢复
77
+
78
+ - 测试失败:版本不变,不上传。
79
+ - 构建、校验或产物复制失败:恢复发布前的版本,不上传。
80
+ - 上传失败或中断:可能已上传部分文件,因此保留递增后的版本和产物,不自动回滚/重试。
81
+
82
+ 上传失败后,先在仓库确认是否已有相同版本。**不要直接再次运行默认发布命令,否则会继续递增版本。**
83
+
84
+ 若确认两个文件都未上传,可用当前版本重新构建并发布:
85
+
86
+ ```bash
87
+ .venv/bin/python upload.py --no-bump
88
+ # 私有仓库仍需传入 --repository internal。
89
+ ```
90
+
91
+ 若只上传了部分文件,核对已存在文件后,使用 `python -m twine upload --non-interactive <尚未上传的确切文件路径>` 补传另一文件,并附上相同仓库参数。不要盲目覆盖同一版本;PyPI 通常不允许同名文件重新上传。不要让多个发布进程并发操作同一个工作目录。
@@ -0,0 +1,16 @@
1
+ """EZR internal database metadata SDK (synchronous and read-only)."""
2
+ from .client import ConnectionInfo, EzrDatabaseSDK
3
+ from .errors import (
4
+ SDKError, ConfigurationError, MetadataNotFoundError,
5
+ AmbiguousMetadataError, DatabaseError,
6
+ )
7
+ from .routing import (
8
+ get_code, physical_table_name, physical_table_name_by_int, physical_table_name_by_hash,
9
+ )
10
+
11
+ __all__ = [
12
+ 'EzrDatabaseSDK', 'ConnectionInfo', 'SDKError', 'ConfigurationError',
13
+ 'MetadataNotFoundError', 'AmbiguousMetadataError', 'DatabaseError',
14
+ 'get_code', 'physical_table_name_by_int', 'physical_table_name_by_hash',
15
+ 'physical_table_name', # Deprecated compatibility entry point.
16
+ ]