wedata3-feature-engineering 0.2.6.dev2__tar.gz → 0.2.6.dev4__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 (103) hide show
  1. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/PKG-INFO +6 -25
  2. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/cloud_sdk_client/models.py +2 -2
  3. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/constants/constants.py +1 -1
  4. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/online_store/packed_codec.py +87 -6
  5. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/feature_engineering/client.py +1 -1
  6. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/feature_engineering/feast/infra/online_store/cleanup_packed.py +160 -4
  7. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/feature_engineering/feast/infra/online_store/redis_packed.py +55 -15
  8. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/feature_engineering/table_client/table_client.py +9 -9
  9. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata3_feature_engineering.egg-info/PKG-INFO +6 -25
  10. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata3_feature_engineering.egg-info/SOURCES.txt +0 -12
  11. wedata3_feature_engineering-0.2.6.dev2/tests/test_cleanup_packed.py +0 -265
  12. wedata3_feature_engineering-0.2.6.dev2/tests/test_client.py +0 -1722
  13. wedata3_feature_engineering-0.2.6.dev2/tests/test_common_utils.py +0 -8
  14. wedata3_feature_engineering-0.2.6.dev2/tests/test_feast_client_validate_keys.py +0 -190
  15. wedata3_feature_engineering-0.2.6.dev2/tests/test_feature_spec_training_set.py +0 -313
  16. wedata3_feature_engineering-0.2.6.dev2/tests/test_feature_store.py +0 -388
  17. wedata3_feature_engineering-0.2.6.dev2/tests/test_feature_table_client_validate_keys.py +0 -192
  18. wedata3_feature_engineering-0.2.6.dev2/tests/test_packed_codec.py +0 -380
  19. wedata3_feature_engineering-0.2.6.dev2/tests/test_redis_packed.py +0 -378
  20. wedata3_feature_engineering-0.2.6.dev2/tests/test_sync_template.py +0 -243
  21. wedata3_feature_engineering-0.2.6.dev2/tests/test_table_client.py +0 -154
  22. wedata3_feature_engineering-0.2.6.dev2/tests/test_table_client_dispatch.py +0 -223
  23. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/README.md +0 -0
  24. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/setup.cfg +0 -0
  25. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/setup.py +0 -0
  26. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/__init__.py +0 -0
  27. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/__init__.py +0 -0
  28. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/base_table_client/__init__.py +0 -0
  29. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/base_table_client/base.py +0 -0
  30. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/cloud_sdk_client/__init__.py +0 -0
  31. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/cloud_sdk_client/client.py +0 -0
  32. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/cloud_sdk_client/utils.py +0 -0
  33. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/constants/__init__.py +0 -0
  34. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/constants/engine_types.py +0 -0
  35. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/entities/__init__.py +0 -0
  36. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/entities/column_info.py +0 -0
  37. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/entities/environment_variables.py +0 -0
  38. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/entities/feature.py +0 -0
  39. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/entities/feature_column_info.py +0 -0
  40. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/entities/feature_function.py +0 -0
  41. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/entities/feature_lookup.py +0 -0
  42. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/entities/feature_spec.py +0 -0
  43. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/entities/feature_spec_constants.py +0 -0
  44. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/entities/feature_table.py +0 -0
  45. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/entities/feature_table_info.py +0 -0
  46. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/entities/function_info.py +0 -0
  47. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/entities/on_demand_column_info.py +0 -0
  48. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/entities/source_data_column_info.py +0 -0
  49. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/entities/training_set.py +0 -0
  50. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/feast_client/__init__.py +0 -0
  51. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/feast_client/feast_client.py +0 -0
  52. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/log/__init__.py +0 -0
  53. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/log/logger.py +0 -0
  54. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/online_store/__init__.py +0 -0
  55. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/protos/__init__.py +0 -0
  56. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/protos/feature_store_pb2.py +0 -0
  57. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/spark_client/__init__.py +0 -0
  58. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/spark_client/spark_client.py +0 -0
  59. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/utils/__init__.py +0 -0
  60. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/utils/common_utils.py +0 -0
  61. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/utils/env_utils.py +0 -0
  62. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/utils/feature_lookup_utils.py +0 -0
  63. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/utils/feature_spec_utils.py +0 -0
  64. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/utils/feature_utils.py +0 -0
  65. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/utils/on_demand_utils.py +0 -0
  66. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/utils/schema_utils.py +0 -0
  67. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/utils/signature_utils.py +0 -0
  68. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/utils/topological_sort.py +0 -0
  69. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/utils/training_set_utils.py +0 -0
  70. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/utils/uc_utils.py +0 -0
  71. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/common/utils/validation_utils.py +0 -0
  72. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/feature_engineering/__init__.py +0 -0
  73. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/feature_engineering/feast/__init__.py +0 -0
  74. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/feature_engineering/feast/infra/__init__.py +0 -0
  75. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/feature_engineering/feast/infra/online_store/__init__.py +0 -0
  76. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/feature_engineering/feast/infra/online_store/postgres.py +0 -0
  77. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/feature_engineering/ml_training_client/__init__.py +0 -0
  78. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/feature_engineering/ml_training_client/ml_training_client.py +0 -0
  79. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/feature_engineering/mlflow_model.py +0 -0
  80. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/feature_engineering/table_client/__init__.py +0 -0
  81. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/feature_store/__init__.py +0 -0
  82. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/feature_store/client.py +0 -0
  83. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/feature_store/common/__init__.py +0 -0
  84. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/feature_store/common/store_config/__init__.py +0 -0
  85. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/feature_store/common/store_config/redis.py +0 -0
  86. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/feature_store/constants/__init__.py +0 -0
  87. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/feature_store/constants/engine_types.py +0 -0
  88. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/feature_store/feature_table_client/__init__.py +0 -0
  89. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/feature_store/feature_table_client/feature_table_client.py +0 -0
  90. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/feature_store/mlflow_model.py +0 -0
  91. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/feature_store/training_set_client/__init__.py +0 -0
  92. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/feature_store/training_set_client/training_set_client.py +0 -0
  93. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/tempo/__init__.py +0 -0
  94. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/tempo/interpol.py +0 -0
  95. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/tempo/intervals.py +0 -0
  96. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/tempo/io.py +0 -0
  97. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/tempo/ml.py +0 -0
  98. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/tempo/resample.py +0 -0
  99. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/tempo/tsdf.py +0 -0
  100. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata/tempo/utils.py +0 -0
  101. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata3_feature_engineering.egg-info/dependency_links.txt +0 -0
  102. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata3_feature_engineering.egg-info/requires.txt +0 -0
  103. {wedata3_feature_engineering-0.2.6.dev2 → wedata3-feature-engineering-0.2.6.dev4}/wedata3_feature_engineering.egg-info/top_level.txt +0 -0
@@ -1,41 +1,20 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.1
2
2
  Name: wedata3-feature-engineering
3
- Version: 0.2.6.dev2
3
+ Version: 0.2.6.dev4
4
4
  Summary: Wedata Feature Engineering Library Development
5
- Home-page:
5
+ Home-page: UNKNOWN
6
6
  Author: meahqian
7
7
  Author-email:
8
8
  License: Apache 2.0
9
+ Platform: UNKNOWN
9
10
  Classifier: Programming Language :: Python :: 3
10
11
  Classifier: License :: OSI Approved :: Apache Software License
11
12
  Classifier: Operating System :: OS Independent
12
13
  Requires-Python: >=3.10
13
14
  Description-Content-Type: text/markdown
14
- Requires-Dist: pandas>=1.0.0
15
- Requires-Dist: feast[postgres,redis]==0.49.0
16
- Requires-Dist: grpcio==1.74.0
17
- Requires-Dist: msgpack<2,>=1.0
18
- Requires-Dist: tencentcloud-sdk-python
19
- Requires-Dist: ipython
20
- Requires-Dist: msgpack<2.0.0,>=1.0.0
21
15
  Provides-Extra: mlflow2
22
- Requires-Dist: mlflow==2.17.2; extra == "mlflow2"
23
16
  Provides-Extra: mlflow3
24
- Requires-Dist: mlflow<3.11.0,>=3.10.0; extra == "mlflow3"
25
17
  Provides-Extra: dev
26
- Requires-Dist: flake8; extra == "dev"
27
- Requires-Dist: pytest; extra == "dev"
28
- Requires-Dist: python-dotenv; extra == "dev"
29
- Requires-Dist: fakeredis[lua]; extra == "dev"
30
- Dynamic: author
31
- Dynamic: classifier
32
- Dynamic: description
33
- Dynamic: description-content-type
34
- Dynamic: license
35
- Dynamic: provides-extra
36
- Dynamic: requires-dist
37
- Dynamic: requires-python
38
- Dynamic: summary
39
18
 
40
19
  # wedata3-feature-engineering
41
20
 
@@ -160,3 +139,5 @@ bash build.sh
160
139
  ## 许可证
161
140
 
162
141
  Apache License 2.0
142
+
143
+
@@ -1184,7 +1184,7 @@ class CreateOnlineTableRequest(AbstractModel):
1184
1184
  TargetPath: 目标路径
1185
1185
  OfflineFeatureConfiguration: 离线表信息
1186
1186
  TaskSchedulerConfiguration: 任务调度配置
1187
- OnlineStoreType: 在线存储类型(TENCENT_POSTGRESQL | REDIS,缺省 TENCENT_POSTGRESQL 保证旧调用方兼容)
1187
+ OnlineStoreType: 在线存储类型(TENCENT_POSTGRE | REDIS,缺省 TENCENT_POSTGRE)
1188
1188
  """
1189
1189
 
1190
1190
  def __init__(self) -> None:
@@ -1351,7 +1351,7 @@ class DeleteOnlineTableRequest(AbstractModel):
1351
1351
  WorkspaceId: 工作空间ID
1352
1352
  TableNameInfo: 表信息
1353
1353
  ResourceGroupId: 资源组ID
1354
- OnlineStoreType: 在线存储类型(TENCENT_POSTGRESQL | REDIS,缺省 TENCENT_POSTGRESQL 保证旧调用方兼容)
1354
+ OnlineStoreType: 在线存储类型(TENCENT_POSTGRE | REDIS,缺省 TENCENT_POSTGRE)
1355
1355
  """
1356
1356
  def __init__(self) -> None:
1357
1357
  self._WorkspaceId = None
@@ -46,7 +46,7 @@ FEATURE_LOOKUP_CLIENT_PIP_PACKAGE = "wedata3-feature-engineering"
46
46
  # 特征查找版本号
47
47
  # FEATURE_LOOKUP_CLIENT_MAJOR_VERSION = "0.6.16"
48
48
  # FEATURE_LOOKUP_CLIENT_PIP_PACKAGE_VERSION = f"{FEATURE_LOOKUP_CLIENT_PIP_PACKAGE}>=0.6.16"
49
- FEATURE_LOOKUP_CLIENT_MAJOR_VERSION = "0.2.6.dev2"
49
+ FEATURE_LOOKUP_CLIENT_MAJOR_VERSION = "0.2.6.dev4"
50
50
  FEATURE_LOOKUP_CLIENT_PIP_PACKAGE_VERSION = f"{FEATURE_LOOKUP_CLIENT_PIP_PACKAGE}>=0.2.4"
51
51
 
52
52
  # 特征存储内部数据目录
@@ -50,6 +50,29 @@ _DATA_VERSION_RAND_LEN = 2
50
50
  _OVERSIZE_WARNED: set = set()
51
51
 
52
52
 
53
+ def _check_msgpack_impl() -> None:
54
+ """检测 msgpack 是否走 C 扩展实现(方案 D3.1.1 落地约束:防静默降级)。
55
+
56
+ zipimport 等场景下 msgpack 会静默退回纯 Python 实现,5 亿行同步慢数十倍。
57
+ Executor 上通过 ``spark.executorEnv.PYTHONPATH=./deps`` 分发的归档若平台 ABI
58
+ 不匹配即触发该情况,必须在运行时告警而不是等同步超时才发现。
59
+ """
60
+ try:
61
+ import msgpack
62
+
63
+ if "fallback" in str(msgpack.Packer):
64
+ logger.warning(
65
+ "msgpack is running in PURE-PYTHON fallback mode (C extension not loaded); "
66
+ "redis packed sync will be extremely slow. Check that the executor deps archive "
67
+ "matches the executor platform/ABI (see scripts/build_executor_deps.sh)."
68
+ )
69
+ except Exception: # pragma: no cover - 检测本身不允许影响主流程
70
+ logger.warning("msgpack implementation check skipped (import failed)", exc_info=True)
71
+
72
+
73
+ _check_msgpack_impl()
74
+
75
+
53
76
  class PackedCodecError(ValueError):
54
77
  """编解码层的显式失败(对应 codec 设计 §8 的 F2/F3/F8 等)。"""
55
78
 
@@ -389,19 +412,58 @@ def _encode_value(vtype: str, v: Any) -> Any:
389
412
  return None
390
413
  if isinstance(v, float) and math.isnan(v):
391
414
  return None
392
- if vtype == "UNIX_TIMESTAMP" and isinstance(v, _dt.datetime):
415
+ if isinstance(v, Decimal):
416
+ return _encode_decimal(vtype, v)
417
+ if isinstance(v, _dt.date):
418
+ return _encode_temporal(vtype, v)
419
+ return v
420
+
421
+
422
+ def _encode_decimal(vtype: str, v: Decimal) -> Any:
423
+ """Spark DecimalType → Decimal。Java 侧把 decimal 映射为 STRING(未知类型降级),msgpack 不能直接序列化 Decimal。"""
424
+ if not v.is_finite():
425
+ return None
426
+ if vtype == "STRING":
427
+ return format(v, "f") # 保留小数位、不出现科学计数法(Decimal('1E+2') → '100')
428
+ if vtype in ("DOUBLE", "FLOAT"):
429
+ return float(v)
430
+ if vtype in ("INT32", "INT64"):
431
+ if v != v.to_integral_value():
432
+ raise PackedCodecError(f"小数 {v} 不能写入整数类型 {vtype}")
433
+ return int(v)
434
+ raise PackedCodecError(f"Decimal 不能写入 {vtype} 类型")
435
+
436
+
437
+ def _encode_temporal(vtype: str, v: _dt.date) -> Any:
438
+ """datetime / date。UNIX_TIMESTAMP 存 epoch 秒;Java 把 date 映射为 STRING,按 ISO 格式存。"""
439
+ if vtype == "UNIX_TIMESTAMP":
440
+ if not isinstance(v, _dt.datetime):
441
+ v = _dt.datetime(v.year, v.month, v.day) # date 按当天 0 点
393
442
  if v.tzinfo is None:
394
443
  v = v.replace(tzinfo=_dt.timezone.utc)
395
444
  return int(v.timestamp())
396
- return v
445
+ if vtype == "STRING":
446
+ return v.isoformat()
447
+ raise PackedCodecError(f"{type(v).__name__} 不能写入 {vtype} 类型")
397
448
 
398
449
 
399
450
  def encode_row(meta: SchemaMeta, ts_micros: Optional[int], values: Mapping[str, Any]) -> bytes:
400
451
  """dict → 位置数组 msgpack;缺失字段编码为 nil。下标 0 固定为 event_ts_micros。"""
401
452
  arr: List[Any] = [None if ts_micros is None else int(ts_micros)]
402
453
  for name, vtype in meta.fields:
403
- arr.append(_encode_value(vtype, values.get(name)))
404
- return msgpack.packb(arr, use_bin_type=True)
454
+ try:
455
+ arr.append(_encode_value(vtype, values.get(name)))
456
+ except PackedCodecError as e:
457
+ raise PackedCodecError(f"特征 {name} 编码失败: {e}") from None
458
+ try:
459
+ return msgpack.packb(arr, use_bin_type=True)
460
+ except TypeError as e:
461
+ # 兜底:出现 codec 没覆盖的类型时报清楚是哪一列,而不是裸 msgpack 错误
462
+ bad = [n for (n, _), x in zip(meta.fields, arr[1:]) if not isinstance(x, _MSGPACK_SCALARS)]
463
+ raise PackedCodecError(f"特征 {bad} 的值类型不支持序列化: {e}") from None
464
+
465
+
466
+ _MSGPACK_SCALARS = (type(None), bool, int, float, str, bytes, bytearray)
405
467
 
406
468
 
407
469
  def decode_row(
@@ -617,20 +679,35 @@ ACTIVATE_LUA = r"""
617
679
  -- 返回 {switched(1/0), previous_active or "", reason}
618
680
  local new_member = ARGV[1]
619
681
  local now = tonumber(ARGV[2])
682
+ -- 水位超过 now + 1 天视为脏数据(未来时间戳),按 0 参与比较:
683
+ -- 否则一条脏数据会让之后所有批次都被判旧(与 WATERMARK_FUTURE_TOLERANCE_SECONDS 一致)
684
+ local MAX_FUTURE_SECONDS = 86400
685
+ local function watermark_of(info)
686
+ if info.watermark > now + MAX_FUTURE_SECONDS then
687
+ return 0
688
+ end
689
+ return info.watermark
690
+ end
620
691
 
621
692
  local new_raw = redis.call('HGET', KEYS[2], new_member)
622
693
  if not new_raw then
623
694
  return {0, '', 'NEW_NOT_REGISTERED'}
624
695
  end
625
696
  local new_info = cjson.decode(new_raw)
697
+ -- 只激活写入中的批次:超时被判 ABANDONED、已 REJECTED / PURGED 的批次数据可能已被清理,激活即全量 miss
698
+ if new_info.state ~= 'WRITING' then
699
+ return {0, redis.call('GET', KEYS[1]) or '', 'NOT_WRITING'}
700
+ end
626
701
 
627
702
  local cur_member = redis.call('GET', KEYS[1])
628
703
  if cur_member then
629
704
  local cur_raw = redis.call('HGET', KEYS[2], cur_member)
630
705
  if cur_raw then
631
706
  local cur = cjson.decode(cur_raw)
632
- local newer = (new_info.watermark > cur.watermark) or
633
- (new_info.watermark == cur.watermark and new_info.run_start > cur.run_start)
707
+ local new_wm = watermark_of(new_info)
708
+ local cur_wm = watermark_of(cur)
709
+ local newer = (new_wm > cur_wm) or
710
+ (new_wm == cur_wm and new_info.run_start > cur.run_start)
634
711
  if not newer then
635
712
  new_info.state = 'STALE'
636
713
  new_info.updated_at = now
@@ -651,3 +728,7 @@ new_info.updated_at = now
651
728
  redis.call('HSET', KEYS[2], new_member, cjson.encode(new_info))
652
729
  return {1, cur_member or '', 'SWITCHED'}
653
730
  """
731
+
732
+ #: 批次水位允许超出当前时间的上限(秒)。同步模板登记时超出即截断为当前时间并告警,ACTIVATE_LUA 比较时超出按 0 处理。
733
+ #: 1 天能容纳时区错配(P1-13 的 8 小时偏移)与机器时钟偏差。
734
+ WATERMARK_FUTURE_TOLERANCE_SECONDS = 86400
@@ -1095,7 +1095,7 @@ class FeatureEngineeringClient:
1095
1095
  由用户根据所在工作空间已配置的在线 catalog 显式指定(如 `DataLakeCatalog`),
1096
1096
  SDK 不提供默认兜底以避免在多 catalog 场景下静默连接错误的 PG 实例。Redis 不传
1097
1097
  trigger: [可选]周期性任务的调度配置
1098
- online_store_type: [可选]"TENCENT_POSTGRESQL"(默认)或 "REDIS"。Redis 在线表由离线表与
1098
+ online_store_type: [可选]"TENCENT_POSTGRE"(默认)或 "REDIS"。Redis 在线表由离线表与
1099
1099
  工作空间的 Redis 数据源确定,不需要也不接受在线库表名
1100
1100
  """
1101
1101
  kwargs = {}
@@ -15,6 +15,7 @@ key 格式与 BatchInfo 编解码统一走 ``wedata.common.online_store.packed_c
15
15
  | 状态 | 处置 |
16
16
  |---|---|
17
17
  | WRITING(未超时 <6h) | 不清理(同步进行中) |
18
+ | WRITING(作业失败收尾)| ``reject_batch`` 置 REJECTED 并立即清理,不等 6h |
18
19
  | WRITING(超时 ≥6h)| 转 ABANDONED 后立即清理 |
19
20
  | ACTIVE | **永不清理**(二次确认防误删) |
20
21
  | RETIRED(最新 1 个,retired <1h) | 保留(回滚窗口) |
@@ -30,6 +31,7 @@ from __future__ import annotations
30
31
 
31
32
  import logging
32
33
  import re
34
+ import threading
33
35
  import time
34
36
  from typing import Any, Dict, List, Optional, Tuple
35
37
 
@@ -134,14 +136,15 @@ def cleanup(client: redis.Redis, prefix: str) -> int:
134
136
  if state in (pc.STATE_REJECTED, pc.STATE_STALE, pc.STATE_ABANDONED):
135
137
  to_delete.append((member, info))
136
138
  elif state == pc.STATE_WRITING and _is_abandoned(info, now):
137
- _mark_state(client, prefix, member, pc.STATE_ABANDONED, now)
138
- to_delete.append((member, info))
139
+ # 原子判定:期间批次被激活或续了心跳就不动它(快照判超时、再非原子改状态会把刚激活的批次删掉)
140
+ if _mark_abandoned(client, prefix, member, info, now):
141
+ to_delete.append((member, info))
139
142
  # ACTIVE / 未超时 WRITING / PURGED:不删数据
140
143
 
141
144
  deleted = 0
142
145
  for member, _info in to_delete:
143
- if member == active_member:
144
- # 防御性检查:理论不可能(ACTIVE 不在 to_delete),双重保险
146
+ # active 以删除前实时读取为准,不用上面的快照:快照之后可能已切换
147
+ if member in (active_member, _get_active_member(client, prefix)):
145
148
  logger.warning("skip purge of active batch %r on %r", member, prefix)
146
149
  continue
147
150
  try:
@@ -170,6 +173,149 @@ def purge_batch(client: redis.Redis, prefix: str, member: str) -> None:
170
173
  _mark_state(client, prefix, member, pc.STATE_PURGED, time.time())
171
174
 
172
175
 
176
+ #: 只把仍处于 WRITING 的批次改为 REJECTED,返回改写后的状态(批次不存在返回空串)。
177
+ #: 原子判断:激活脚本已执行但客户端没收到回包时,批次可能已是 ACTIVE,绝不能被改写。
178
+ _REJECT_LUA = r"""
179
+ local raw = redis.call('HGET', KEYS[1], ARGV[1])
180
+ if not raw then
181
+ return ''
182
+ end
183
+ local info = cjson.decode(raw)
184
+ if info.state == 'WRITING' then
185
+ info.state = 'REJECTED'
186
+ info.updated_at = tonumber(ARGV[2])
187
+ info.reason = ARGV[3]
188
+ redis.call('HSET', KEYS[1], ARGV[1], cjson.encode(info))
189
+ end
190
+ return info.state
191
+ """
192
+
193
+ #: 失败收尾时可以立即删除数据的状态(REJECTED 来自本函数,STALE 来自激活脚本判旧)
194
+ _DISCARDABLE_STATES = frozenset({pc.STATE_REJECTED, pc.STATE_STALE})
195
+
196
+
197
+ def reject_batch(client: redis.Redis, prefix: str, member: str, reason: str) -> bool:
198
+ """
199
+ 同步作业失败收尾:本批 WRITING → REJECTED,并立即删除本批数据,返回是否删除了数据。
200
+
201
+ 不收尾的话批次停在 WRITING,下一次同步的门禁要等 ``MAX_RUN_HOURS`` 才放行;
202
+ 激活被判旧(STALE)的批次同样立即删除,不留一份全量数据占内存到下一次同步。
203
+ 已激活(ACTIVE / RETIRED)或已清理的批次不动。
204
+ """
205
+ state = client.eval(
206
+ _REJECT_LUA, 1, pc.versions_key(prefix), member, int(time.time()), (reason or "")[:200]
207
+ )
208
+ state = state.decode() if isinstance(state, bytes) else state
209
+ if state not in _DISCARDABLE_STATES:
210
+ logger.warning("skip rejecting batch %r on %r: state=%r", member, prefix, state)
211
+ return False
212
+ purge_batch(client, prefix, member)
213
+ logger.info("rejected and purged batch %r on %r: %s", member, prefix, reason)
214
+ return True
215
+
216
+
217
+ # ---------------------------------------------------------------------------
218
+ # 写入心跳(长时间同步不被判超时)
219
+ # ---------------------------------------------------------------------------
220
+
221
+ #: 心跳间隔,远小于 MAX_RUN_HOURS:Driver 卡顿几次也不至于被判超时
222
+ HEARTBEAT_INTERVAL_SECONDS = 300
223
+
224
+ #: 心跳最长持续时间:超过即停止续期,交给超时清理,防止失控的 Driver 永久占住门禁
225
+ HEARTBEAT_MAX_SECONDS = 24 * 3600
226
+
227
+ #: 只给仍是 WRITING 的批次刷新 updated_at,返回 1 / 0
228
+ _HEARTBEAT_LUA = r"""
229
+ local raw = redis.call('HGET', KEYS[1], ARGV[1])
230
+ if not raw then
231
+ return 0
232
+ end
233
+ local info = cjson.decode(raw)
234
+ if info.state ~= 'WRITING' then
235
+ return 0
236
+ end
237
+ info.updated_at = tonumber(ARGV[2])
238
+ redis.call('HSET', KEYS[1], ARGV[1], cjson.encode(info))
239
+ return 1
240
+ """
241
+
242
+ #: 仍是 WRITING 且 updated_at 与判超时时看到的一致(期间没被激活、没续心跳)才改为 ABANDONED,返回 1 / 0
243
+ _MARK_ABANDONED_LUA = r"""
244
+ local raw = redis.call('HGET', KEYS[1], ARGV[1])
245
+ if not raw then
246
+ return 0
247
+ end
248
+ local info = cjson.decode(raw)
249
+ if info.state ~= 'WRITING' or (tonumber(info.updated_at) or 0) ~= tonumber(ARGV[2]) then
250
+ return 0
251
+ end
252
+ info.state = 'ABANDONED'
253
+ info.updated_at = tonumber(ARGV[3])
254
+ redis.call('HSET', KEYS[1], ARGV[1], cjson.encode(info))
255
+ return 1
256
+ """
257
+
258
+
259
+ class BatchHeartbeat:
260
+ """
261
+ 写入期间在后台线程里定期刷新批次 ``updated_at``。
262
+
263
+ 不刷新的话,超过 ``MAX_RUN_HOURS`` 的同步会被下一次运行的门禁判为 ABANDONED 并删掉数据,
264
+ 而本次运行随后仍可能激活它,``__active__`` 指向已删除的批次,读侧全量 miss。
265
+ 批次一旦不再是 WRITING(已激活 / 被拒绝 / 被判超时),心跳自动结束。
266
+ """
267
+
268
+ def __init__(
269
+ self,
270
+ client: redis.Redis,
271
+ prefix: str,
272
+ member: str,
273
+ interval_seconds: float = HEARTBEAT_INTERVAL_SECONDS,
274
+ max_seconds: float = HEARTBEAT_MAX_SECONDS,
275
+ ) -> None:
276
+ self._client = client
277
+ self._prefix = prefix
278
+ self._member = member
279
+ self._interval = interval_seconds
280
+ self._max_seconds = max_seconds
281
+ self._stop = threading.Event()
282
+ self._thread = threading.Thread(target=self._run, name=f"batch-heartbeat-{member}", daemon=True)
283
+
284
+ def start(self) -> "BatchHeartbeat":
285
+ self._thread.start()
286
+ return self
287
+
288
+ def beat(self) -> bool:
289
+ """刷新一次,返回批次是否仍在写入中。"""
290
+ return bool(
291
+ self._client.eval(_HEARTBEAT_LUA, 1, pc.versions_key(self._prefix), self._member, int(time.time()))
292
+ )
293
+
294
+ def stop(self) -> None:
295
+ """停止心跳(幂等)。"""
296
+ self._stop.set()
297
+ if self._thread.is_alive() and self._thread is not threading.current_thread():
298
+ self._thread.join(timeout=5)
299
+
300
+ def _run(self) -> None:
301
+ deadline = time.monotonic() + self._max_seconds
302
+ while not self._stop.wait(self._interval):
303
+ if time.monotonic() > deadline:
304
+ logger.warning("heartbeat of batch %r on %r exceeded %ss, stop", self._member, self._prefix,
305
+ self._max_seconds)
306
+ return
307
+ try:
308
+ if not self.beat():
309
+ return
310
+ except Exception as exc: # 单次失败不退出:下一轮再试,超时判定有 MAX_RUN_HOURS 的余量
311
+ logger.warning("heartbeat of batch %r on %r failed: %s", self._member, self._prefix, exc)
312
+
313
+
314
+ def start_heartbeat(client: redis.Redis, prefix: str, member: str, **kwargs: Any) -> BatchHeartbeat:
315
+ """登记批次后立即调用;激活或失败收尾前调用返回值的 ``stop()``。"""
316
+ return BatchHeartbeat(client, prefix, member, **kwargs).start()
317
+
318
+
173
319
  # ---------------------------------------------------------------------------
174
320
  # 整表删除(删除在线表的一次性清理作业 / SDK delete_table 共用)
175
321
  # ---------------------------------------------------------------------------
@@ -273,6 +419,16 @@ def _is_abandoned(info: Dict[str, Any], now: float) -> bool:
273
419
  return (now - updated) > MAX_RUN_HOURS * 3600
274
420
 
275
421
 
422
+ def _mark_abandoned(client: redis.Redis, prefix: str, member: str, seen: Dict[str, Any], now: float) -> bool:
423
+ """超时的 WRITING → ABANDONED(原子比较 updated_at),返回是否标记成功。"""
424
+ return bool(
425
+ client.eval(
426
+ _MARK_ABANDONED_LUA, 1, pc.versions_key(prefix), member,
427
+ int(seen.get(pc.BATCH_UPDATED_AT, 0)), int(now),
428
+ )
429
+ )
430
+
431
+
276
432
  def _get_active_member(client: redis.Redis, prefix: str) -> Optional[str]:
277
433
  member = client.get(pc.active_key(prefix))
278
434
  if member is None:
@@ -179,15 +179,61 @@ class WedataRedisPackedOnlineStore(OnlineStore):
179
179
  # ------------------------------------------------------------------
180
180
 
181
181
  @staticmethod
182
- def serialize_entity_key(entity_key_proto: EntityKeyProto) -> str:
183
- """EntityKeyProto → 实体键 ek(按 join_keys 顺序取值,转义后用 ``|`` 连接,codec 设计 §2.6.3)。"""
182
+ def serialize_entity_key(entity_key_proto: EntityKeyProto, order: Optional[Sequence[int]] = None) -> str:
183
+ """
184
+ EntityKeyProto → 实体键 ek(转义后用 ``|`` 连接,codec 设计 §2.6.3)。
185
+
186
+ :param order: 按 ``meta.entity_keys`` 顺序取值的下标(见 :meth:`_entity_value_order`);
187
+ None 表示 join_keys 顺序与 meta 一致,按原顺序取值
188
+ """
184
189
  values = []
185
190
  for val in entity_key_proto.entity_values:
186
191
  if val.WhichOneof("val") is None:
187
192
  raise pc.PackedCodecError("entity value not set (None)")
188
193
  values.append(_proto_value_to_py(val))
194
+ if order is not None:
195
+ values = [values[i] for i in order]
189
196
  return pc.build_entity_key_from_values(values)
190
197
 
198
+ @staticmethod
199
+ def _entity_value_order(
200
+ join_keys: Sequence[str], meta: SchemaMeta, table_name: str, prefix: str
201
+ ) -> Optional[List[int]]:
202
+ """
203
+ F8:join_keys 与建表时的 ``meta.entity_keys`` 比对。
204
+
205
+ 集合相同、顺序不同(如请求写成 ``{"b": 1, "a": 2}``,feast 按请求键序构造 join_keys)是合法请求,
206
+ 返回按 meta 顺序取值的下标;顺序一致或未带 join_keys 返回 None;集合不同(registry 改名 / 传错 FV)抛错,
207
+ 否则会拼出错误的 ek 静默 100% miss。
208
+ """
209
+ if not join_keys:
210
+ return None
211
+ keys = tuple(join_keys)
212
+ expected = tuple(meta.entity_keys)
213
+ if keys == expected:
214
+ return None
215
+ if len(set(keys)) != len(keys) or set(keys) != set(expected):
216
+ raise pc.PackedCodecError(
217
+ f"entity join_keys {list(keys)} do not match online table "
218
+ f"{table_name} (meta entity_keys={list(expected)}, prefix={prefix!r}); "
219
+ f"the feature view / registry has drifted from the published table"
220
+ )
221
+ position = {k: i for i, k in enumerate(keys)}
222
+ return [position[k] for k in expected]
223
+
224
+ def _serialize_entity_keys(
225
+ self, entity_keys: Sequence[EntityKeyProto], meta: SchemaMeta, table_name: str, prefix: str
226
+ ) -> List[str]:
227
+ """按 meta 顺序批量构造 ek;同一批里 join_keys 顺序相同的只比对一次。"""
228
+ orders: Dict[Tuple[str, ...], Optional[List[int]]] = {}
229
+ out = []
230
+ for ek in entity_keys:
231
+ join_keys = tuple(ek.join_keys)
232
+ if join_keys not in orders:
233
+ orders[join_keys] = self._entity_value_order(join_keys, meta, table_name, prefix)
234
+ out.append(self.serialize_entity_key(ek, orders[join_keys]))
235
+ return out
236
+
191
237
  # ------------------------------------------------------------------
192
238
  # 读路径(稳态 1 次 MGET)
193
239
  # ------------------------------------------------------------------
@@ -259,16 +305,8 @@ class WedataRedisPackedOnlineStore(OnlineStore):
259
305
  sv, dv = pc.parse_active_pointer(member)
260
306
  meta = self._get_meta(client, prefix, sv)
261
307
 
262
- # F8:entity join_keys 与建表时的 meta.entity_keys 不一致(registry 重命名/重排/传错 FV)
263
- # → 抛错。否则按错误顺序拼 ek 会静默 100% miss。
264
- if entity_keys[0].join_keys:
265
- read_join_keys = tuple(entity_keys[0].join_keys)
266
- if read_join_keys != tuple(meta.entity_keys):
267
- raise pc.PackedCodecError(
268
- f"entity join_keys {list(read_join_keys)} do not match online table "
269
- f"{table.name} (meta entity_keys={list(meta.entity_keys)}, prefix={prefix!r}); "
270
- f"the feature view / registry has drifted from the published table"
271
- )
308
+ # F8:join_keys 与 meta.entity_keys 集合不同则抛错,顺序不同按 meta 顺序重排(见 _entity_value_order)
309
+ eks = self._serialize_entity_keys(entity_keys, meta, table.name, prefix)
272
310
 
273
311
  # 校验 requested_features ⊆ meta fields(防拼写错误静默丢列)
274
312
  if requested_features:
@@ -278,7 +316,7 @@ class WedataRedisPackedOnlineStore(OnlineStore):
278
316
  f"requested features not in online table {table.name}: {sorted(unknown)}"
279
317
  )
280
318
 
281
- keys = [pc.data_key(prefix, sv, dv, self.serialize_entity_key(ek)) for ek in entity_keys]
319
+ keys = [pc.data_key(prefix, sv, dv, ek) for ek in eks]
282
320
  blobs = client.mget(keys)
283
321
 
284
322
  result: List[Tuple[Optional[datetime], Optional[Dict[str, ValueProto]]]] = []
@@ -326,6 +364,8 @@ class WedataRedisPackedOnlineStore(OnlineStore):
326
364
  meta = pc.build_meta_from_feature_view(table, source_table=_source_table(table))
327
365
  ttl = int(getattr(config.online_store, "ttl_seconds", 86400 * 30))
328
366
  max_bytes = int(getattr(config.online_store, "max_string_bytes", pc.DEFAULT_MAX_STRING_BYTES))
367
+ # 与读路径同一规则按 meta 顺序构造 ek;放在登记批次之前,主键非法时不留下 WRITING 批次
368
+ eks = self._serialize_entity_keys([d[0] for d in data], meta, table.name, prefix)
329
369
 
330
370
  member = self._get_active(client, prefix)
331
371
  with self._cache_lock: # 写路径直接失效读缓存,保证写后读一致
@@ -350,14 +390,14 @@ class WedataRedisPackedOnlineStore(OnlineStore):
350
390
  member = pc.build_batch_member(sv, dv)
351
391
 
352
392
  pipe = client.pipeline(transaction=False)
353
- for entity_key_proto, feature_map, event_ts, _created_ts in data:
393
+ for ek, (_entity_key_proto, feature_map, event_ts, _created_ts) in zip(eks, data):
354
394
  values = {
355
395
  name: _proto_value_to_py(feature_map[name])
356
396
  for name in meta.field_names
357
397
  if name in feature_map
358
398
  }
359
399
  pc.validate_string_lengths(values, max_bytes)
360
- key = pc.data_key(prefix, sv, dv, self.serialize_entity_key(entity_key_proto))
400
+ key = pc.data_key(prefix, sv, dv, ek)
361
401
  ts_micros = None if event_ts is None else event_time_to_micros(event_ts)
362
402
  pipe.set(key, pc.encode_row(meta, ts_micros, values), ex=ttl)
363
403
  pipe.execute()
@@ -813,7 +813,7 @@ class FeatureEngineeringTableClient(AbstractBaseTableClient):
813
813
  Properties 中没有 connectionId,则降级走 TCCatalog 模式。常见取值如 `DataLakeCatalog`,
814
814
  具体需结合所在工作空间已配置的在线 catalog 决定,禁止由 SDK 默认兜底。Redis 不传
815
815
  trigger: [可选]周期性任务的调度配置
816
- online_store_type: [可选]在线存储类型,"TENCENT_POSTGRESQL"(默认,向后兼容)或 "REDIS"。
816
+ online_store_type: [可选]在线存储类型,"TENCENT_POSTGRE"(默认)或 "REDIS"。
817
817
  传 "REDIS" 时:在线表由离线表与 Redis 数据源确定,不需要在线库表名(传了即报错);
818
818
  不支持持续同步(trigger 的 TriggerMode=CONTINUE_RUN 即报错),只支持 SNAPSHOT / TIME_TRIGGER;
819
819
  同一离线表只能发布到 PG 或 Redis 其中一种(服务端校验);
@@ -830,11 +830,11 @@ class FeatureEngineeringTableClient(AbstractBaseTableClient):
830
830
  """
831
831
  # store 类型分派(不传 = PG,行为与历史版本完全一致)
832
832
  if online_store_type is None:
833
- online_store_type = "TENCENT_POSTGRESQL"
833
+ online_store_type = "TENCENT_POSTGRE"
834
834
  online_store_type = online_store_type.upper()
835
- if online_store_type not in ("TENCENT_POSTGRESQL", "REDIS"):
835
+ if online_store_type not in ("TENCENT_POSTGRE", "REDIS"):
836
836
  raise ValueError(
837
- f"online_store_type must be 'TENCENT_POSTGRESQL' or 'REDIS', got {online_store_type!r}"
837
+ f"online_store_type must be 'TENCENT_POSTGRE' or 'REDIS', got {online_store_type!r}"
838
838
  )
839
839
  online_names = {
840
840
  "online_db_name": online_db_name,
@@ -868,12 +868,12 @@ class FeatureEngineeringTableClient(AbstractBaseTableClient):
868
868
  ]
869
869
  if given_redis:
870
870
  raise ValueError(
871
- f"{', '.join(given_redis)} must not be set for TENCENT_POSTGRESQL: "
871
+ f"{', '.join(given_redis)} must not be set for TENCENT_POSTGRE: "
872
872
  f"the PG connection is resolved from online_catalog_name on the server side"
873
873
  )
874
874
  missing = [k for k, v in online_names.items() if not v]
875
875
  if missing:
876
- raise ValueError(f"{', '.join(missing)} is required for TENCENT_POSTGRESQL online table")
876
+ raise ValueError(f"{', '.join(missing)} is required for TENCENT_POSTGRE online table")
877
877
 
878
878
  # 构建完整表名
879
879
  full_table_name = common_utils.build_full_table_name(table_name, schema_name, catalog_name, self._spark)
@@ -1037,16 +1037,16 @@ class FeatureEngineeringTableClient(AbstractBaseTableClient):
1037
1037
  catalog_name: 数据目录名称
1038
1038
  schema_name: 数据库名称
1039
1039
  table_name: 表名
1040
- online_store_type: [可选]在线存储类型,"TENCENT_POSTGRESQL" 或 "REDIS"。不传时由服务端按标签判定;
1040
+ online_store_type: [可选]在线存储类型,"TENCENT_POSTGRE" 或 "REDIS"。不传时由服务端按标签判定;
1041
1041
  传了也以标签为准(不一致时服务端告警)。
1042
1042
  Raises:
1043
1043
  RuntimeError: 当删除操作失败时抛出
1044
1044
  """
1045
1045
  if online_store_type is not None:
1046
1046
  online_store_type = online_store_type.upper()
1047
- if online_store_type not in ("TENCENT_POSTGRESQL", "REDIS"):
1047
+ if online_store_type not in ("TENCENT_POSTGRE", "REDIS"):
1048
1048
  raise ValueError(
1049
- f"online_store_type must be 'TENCENT_POSTGRESQL' or 'REDIS', got {online_store_type!r}"
1049
+ f"online_store_type must be 'TENCENT_POSTGRE' or 'REDIS', got {online_store_type!r}"
1050
1050
  )
1051
1051
 
1052
1052
  # 接口分页参数
@@ -1,41 +1,20 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.1
2
2
  Name: wedata3-feature-engineering
3
- Version: 0.2.6.dev2
3
+ Version: 0.2.6.dev4
4
4
  Summary: Wedata Feature Engineering Library Development
5
- Home-page:
5
+ Home-page: UNKNOWN
6
6
  Author: meahqian
7
7
  Author-email:
8
8
  License: Apache 2.0
9
+ Platform: UNKNOWN
9
10
  Classifier: Programming Language :: Python :: 3
10
11
  Classifier: License :: OSI Approved :: Apache Software License
11
12
  Classifier: Operating System :: OS Independent
12
13
  Requires-Python: >=3.10
13
14
  Description-Content-Type: text/markdown
14
- Requires-Dist: pandas>=1.0.0
15
- Requires-Dist: feast[postgres,redis]==0.49.0
16
- Requires-Dist: grpcio==1.74.0
17
- Requires-Dist: msgpack<2,>=1.0
18
- Requires-Dist: tencentcloud-sdk-python
19
- Requires-Dist: ipython
20
- Requires-Dist: msgpack<2.0.0,>=1.0.0
21
15
  Provides-Extra: mlflow2
22
- Requires-Dist: mlflow==2.17.2; extra == "mlflow2"
23
16
  Provides-Extra: mlflow3
24
- Requires-Dist: mlflow<3.11.0,>=3.10.0; extra == "mlflow3"
25
17
  Provides-Extra: dev
26
- Requires-Dist: flake8; extra == "dev"
27
- Requires-Dist: pytest; extra == "dev"
28
- Requires-Dist: python-dotenv; extra == "dev"
29
- Requires-Dist: fakeredis[lua]; extra == "dev"
30
- Dynamic: author
31
- Dynamic: classifier
32
- Dynamic: description
33
- Dynamic: description-content-type
34
- Dynamic: license
35
- Dynamic: provides-extra
36
- Dynamic: requires-dist
37
- Dynamic: requires-python
38
- Dynamic: summary
39
18
 
40
19
  # wedata3-feature-engineering
41
20
 
@@ -160,3 +139,5 @@ bash build.sh
160
139
  ## 许可证
161
140
 
162
141
  Apache License 2.0
142
+
143
+