ice-rules 2.0.1__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 (42) hide show
  1. ice_rules-2.0.1/PKG-INFO +103 -0
  2. ice_rules-2.0.1/README.md +83 -0
  3. ice_rules-2.0.1/pyproject.toml +35 -0
  4. ice_rules-2.0.1/setup.cfg +4 -0
  5. ice_rules-2.0.1/src/ice/__init__.py +66 -0
  6. ice_rules-2.0.1/src/ice/_internal/__init__.py +2 -0
  7. ice_rules-2.0.1/src/ice/_internal/executor.py +82 -0
  8. ice_rules-2.0.1/src/ice/_internal/timeutil.py +51 -0
  9. ice_rules-2.0.1/src/ice/_internal/uuid.py +80 -0
  10. ice_rules-2.0.1/src/ice/cache/__init__.py +6 -0
  11. ice_rules-2.0.1/src/ice/cache/conf_cache.py +347 -0
  12. ice_rules-2.0.1/src/ice/cache/handler_cache.py +207 -0
  13. ice_rules-2.0.1/src/ice/client/__init__.py +7 -0
  14. ice_rules-2.0.1/src/ice/client/async_file_client.py +397 -0
  15. ice_rules-2.0.1/src/ice/client/file_client.py +422 -0
  16. ice_rules-2.0.1/src/ice/context/__init__.py +8 -0
  17. ice_rules-2.0.1/src/ice/context/context.py +38 -0
  18. ice_rules-2.0.1/src/ice/context/pack.py +121 -0
  19. ice_rules-2.0.1/src/ice/context/roam.py +206 -0
  20. ice_rules-2.0.1/src/ice/dispatcher.py +202 -0
  21. ice_rules-2.0.1/src/ice/dto.py +102 -0
  22. ice_rules-2.0.1/src/ice/enums.py +57 -0
  23. ice_rules-2.0.1/src/ice/handler/__init__.py +6 -0
  24. ice_rules-2.0.1/src/ice/handler/handler.py +117 -0
  25. ice_rules-2.0.1/src/ice/leaf/__init__.py +6 -0
  26. ice_rules-2.0.1/src/ice/leaf/registry.py +467 -0
  27. ice_rules-2.0.1/src/ice/log.py +106 -0
  28. ice_rules-2.0.1/src/ice/node/__init__.py +8 -0
  29. ice_rules-2.0.1/src/ice/node/base.py +192 -0
  30. ice_rules-2.0.1/src/ice/node/leaf.py +15 -0
  31. ice_rules-2.0.1/src/ice/node/relation.py +53 -0
  32. ice_rules-2.0.1/src/ice/relation/__init__.py +12 -0
  33. ice_rules-2.0.1/src/ice/relation/parallel.py +226 -0
  34. ice_rules-2.0.1/src/ice/relation/serial.py +165 -0
  35. ice_rules-2.0.1/src/ice_rules.egg-info/PKG-INFO +103 -0
  36. ice_rules-2.0.1/src/ice_rules.egg-info/SOURCES.txt +40 -0
  37. ice_rules-2.0.1/src/ice_rules.egg-info/dependency_links.txt +1 -0
  38. ice_rules-2.0.1/src/ice_rules.egg-info/top_level.txt +1 -0
  39. ice_rules-2.0.1/tests/__init__.py +2 -0
  40. ice_rules-2.0.1/tests/test_context.py +176 -0
  41. ice_rules-2.0.1/tests/test_leaf.py +142 -0
  42. ice_rules-2.0.1/tests/test_relation.py +172 -0
@@ -0,0 +1,103 @@
1
+ Metadata-Version: 2.4
2
+ Name: ice-rules
3
+ Version: 2.0.1
4
+ Summary: Ice Rule Engine Python SDK
5
+ Author: waitmoon
6
+ License: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/waitmoon/ice
8
+ Project-URL: Documentation, https://waitmoon.github.io/ice
9
+ Project-URL: Repository, https://github.com/waitmoon/ice
10
+ Keywords: ice,rule-engine,decision-engine
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: Apache Software License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Requires-Python: >=3.11
19
+ Description-Content-Type: text/markdown
20
+
21
+ # Ice Python SDK
22
+
23
+ Ice 规则引擎的 Python 实现,与 Java ice-core 和 Go SDK 功能一致。
24
+
25
+ ## 安装
26
+
27
+ ```bash
28
+ pip install ice-rules
29
+ ```
30
+
31
+ ## 快速开始
32
+
33
+ ### 1. 注册叶子节点
34
+
35
+ ```python
36
+ import ice
37
+ from ice import Roam
38
+
39
+ @ice.leaf("com.example.ScoreFlow")
40
+ class ScoreFlow:
41
+ threshold: int = 0
42
+
43
+ def do_roam_flow(self, roam: Roam) -> bool:
44
+ return roam.get_int("score", 0) >= self.threshold
45
+ ```
46
+
47
+ ### 2. 启动客户端
48
+
49
+ ```python
50
+ # 同步方式
51
+ client = ice.FileClient(app=1, storage_path="./ice-data")
52
+ client.start()
53
+
54
+ pack = ice.Pack(ice_id=1)
55
+ pack.roam.put("score", 85)
56
+ results = ice.sync_process(pack)
57
+
58
+ client.destroy()
59
+ ```
60
+
61
+ ### 3. 异步方式
62
+
63
+ ```python
64
+ import asyncio
65
+
66
+ async def main():
67
+ client = ice.AsyncFileClient(app=1, storage_path="./ice-data")
68
+ await client.start()
69
+
70
+ pack = ice.Pack(ice_id=1)
71
+ pack.roam.put("score", 85)
72
+ results = await ice.async_process(pack)
73
+
74
+ await client.destroy()
75
+
76
+ asyncio.run(main())
77
+ ```
78
+
79
+ ## 叶子节点类型
80
+
81
+ ### Flow 类型(返回 True/False)
82
+ - `do_flow(ctx: Context) -> bool`
83
+ - `do_pack_flow(pack: Pack) -> bool`
84
+ - `do_roam_flow(roam: Roam) -> bool`
85
+
86
+ ### Result 类型(返回 True/False)
87
+ - `do_result(ctx: Context) -> bool`
88
+ - `do_pack_result(pack: Pack) -> bool`
89
+ - `do_roam_result(roam: Roam) -> bool`
90
+
91
+ ### None 类型(无返回值)
92
+ - `do_none(ctx: Context) -> None`
93
+ - `do_pack_none(pack: Pack) -> None`
94
+ - `do_roam_none(roam: Roam) -> None`
95
+
96
+ ## 版本要求
97
+
98
+ - Python >= 3.11
99
+
100
+ ## License
101
+
102
+ Apache-2.0
103
+
@@ -0,0 +1,83 @@
1
+ # Ice Python SDK
2
+
3
+ Ice 规则引擎的 Python 实现,与 Java ice-core 和 Go SDK 功能一致。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ pip install ice-rules
9
+ ```
10
+
11
+ ## 快速开始
12
+
13
+ ### 1. 注册叶子节点
14
+
15
+ ```python
16
+ import ice
17
+ from ice import Roam
18
+
19
+ @ice.leaf("com.example.ScoreFlow")
20
+ class ScoreFlow:
21
+ threshold: int = 0
22
+
23
+ def do_roam_flow(self, roam: Roam) -> bool:
24
+ return roam.get_int("score", 0) >= self.threshold
25
+ ```
26
+
27
+ ### 2. 启动客户端
28
+
29
+ ```python
30
+ # 同步方式
31
+ client = ice.FileClient(app=1, storage_path="./ice-data")
32
+ client.start()
33
+
34
+ pack = ice.Pack(ice_id=1)
35
+ pack.roam.put("score", 85)
36
+ results = ice.sync_process(pack)
37
+
38
+ client.destroy()
39
+ ```
40
+
41
+ ### 3. 异步方式
42
+
43
+ ```python
44
+ import asyncio
45
+
46
+ async def main():
47
+ client = ice.AsyncFileClient(app=1, storage_path="./ice-data")
48
+ await client.start()
49
+
50
+ pack = ice.Pack(ice_id=1)
51
+ pack.roam.put("score", 85)
52
+ results = await ice.async_process(pack)
53
+
54
+ await client.destroy()
55
+
56
+ asyncio.run(main())
57
+ ```
58
+
59
+ ## 叶子节点类型
60
+
61
+ ### Flow 类型(返回 True/False)
62
+ - `do_flow(ctx: Context) -> bool`
63
+ - `do_pack_flow(pack: Pack) -> bool`
64
+ - `do_roam_flow(roam: Roam) -> bool`
65
+
66
+ ### Result 类型(返回 True/False)
67
+ - `do_result(ctx: Context) -> bool`
68
+ - `do_pack_result(pack: Pack) -> bool`
69
+ - `do_roam_result(roam: Roam) -> bool`
70
+
71
+ ### None 类型(无返回值)
72
+ - `do_none(ctx: Context) -> None`
73
+ - `do_pack_none(pack: Pack) -> None`
74
+ - `do_roam_none(roam: Roam) -> None`
75
+
76
+ ## 版本要求
77
+
78
+ - Python >= 3.11
79
+
80
+ ## License
81
+
82
+ Apache-2.0
83
+
@@ -0,0 +1,35 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "setuptools_scm>=8.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "ice-rules"
7
+ dynamic = ["version"]
8
+ description = "Ice Rule Engine Python SDK"
9
+ readme = "README.md"
10
+ license = {text = "Apache-2.0"}
11
+ requires-python = ">=3.11"
12
+ authors = [
13
+ {name = "waitmoon"}
14
+ ]
15
+ keywords = ["ice", "rule-engine", "decision-engine"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: Apache Software License",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Topic :: Software Development :: Libraries :: Python Modules",
24
+ ]
25
+
26
+ [project.urls]
27
+ Homepage = "https://github.com/waitmoon/ice"
28
+ Documentation = "https://waitmoon.github.io/ice"
29
+ Repository = "https://github.com/waitmoon/ice"
30
+
31
+ [tool.setuptools.packages.find]
32
+ where = ["src"]
33
+
34
+ [tool.setuptools_scm]
35
+ root = "../../.."
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,66 @@
1
+ """
2
+ Ice Rule Engine Python SDK
3
+
4
+ A Python implementation of the Ice rule engine, compatible with Java and Go SDKs.
5
+ """
6
+
7
+ from ice.context.roam import Roam
8
+ from ice.context.pack import Pack
9
+ from ice.context.context import Context
10
+ from ice.enums import RunState, NodeType, TimeType
11
+ from ice.leaf.registry import leaf, register_leaf, get_leaf_nodes, LeafMeta, IceField, IceIgnore, FieldMeta
12
+ from ice.log import Logger, set_logger
13
+ from ice.client.file_client import FileClient
14
+ from ice.client.async_file_client import AsyncFileClient
15
+ from ice.dispatcher import (
16
+ sync_process,
17
+ async_process,
18
+ process_ctx,
19
+ process_single_ctx,
20
+ process_roam,
21
+ process_single_roam,
22
+ get_handler_by_id,
23
+ get_handlers_by_scene,
24
+ )
25
+
26
+ try:
27
+ from importlib.metadata import version
28
+ __version__ = version("ice-rules")
29
+ except Exception:
30
+ __version__ = "0.0.0" # fallback for development
31
+
32
+ __all__ = [
33
+ # Context
34
+ "Roam",
35
+ "Pack",
36
+ "Context",
37
+ # Enums
38
+ "RunState",
39
+ "NodeType",
40
+ "TimeType",
41
+ # Leaf registration
42
+ "leaf",
43
+ "register_leaf",
44
+ "get_leaf_nodes",
45
+ "IceField",
46
+ "IceIgnore",
47
+ "LeafMeta",
48
+ # Logging
49
+ "Logger",
50
+ "set_logger",
51
+ # Clients
52
+ "FileClient",
53
+ "AsyncFileClient",
54
+ # Processing (main)
55
+ "sync_process",
56
+ "async_process",
57
+ # Processing (convenience - matching Java/Go)
58
+ "process_ctx",
59
+ "process_single_ctx",
60
+ "process_roam",
61
+ "process_single_roam",
62
+ # Handler access (matching Go)
63
+ "get_handler_by_id",
64
+ "get_handlers_by_scene",
65
+ ]
66
+
@@ -0,0 +1,2 @@
1
+ """Internal utilities for Ice SDK."""
2
+
@@ -0,0 +1,82 @@
1
+ """Thread pool executor for parallel nodes in Ice SDK."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from concurrent.futures import ThreadPoolExecutor, Future
7
+ from dataclasses import dataclass
8
+ from typing import TYPE_CHECKING
9
+
10
+ if TYPE_CHECKING:
11
+ from ice.context.context import Context
12
+ from ice.node.base import Node
13
+ from ice.enums import RunState
14
+
15
+
16
+ # Global executor
17
+ _executor: ThreadPoolExecutor | None = None
18
+
19
+
20
+ def init_executor(parallelism: int = -1) -> None:
21
+ """
22
+ Initialize the global thread pool executor.
23
+
24
+ Args:
25
+ parallelism: Number of worker threads. -1 means use CPU count.
26
+ """
27
+ global _executor
28
+ if _executor is not None:
29
+ return
30
+
31
+ if parallelism <= 0:
32
+ parallelism = os.cpu_count() or 4
33
+
34
+ _executor = ThreadPoolExecutor(max_workers=parallelism, thread_name_prefix="ice-worker")
35
+
36
+
37
+ def shutdown_executor() -> None:
38
+ """Shutdown the global executor."""
39
+ global _executor
40
+ if _executor is not None:
41
+ _executor.shutdown(wait=True)
42
+ _executor = None
43
+
44
+
45
+ def get_executor() -> ThreadPoolExecutor:
46
+ """Get the global executor, initializing if needed."""
47
+ global _executor
48
+ if _executor is None:
49
+ init_executor()
50
+ return _executor # type: ignore
51
+
52
+
53
+ @dataclass
54
+ class NodeResult:
55
+ """Result of a node execution."""
56
+ state: RunState
57
+ error: Exception | None = None
58
+
59
+
60
+ def submit_node(node: Node, ctx: Context) -> Future[NodeResult]:
61
+ """
62
+ Submit a node for execution in the thread pool.
63
+
64
+ Args:
65
+ node: The node to execute
66
+ ctx: The execution context (should be a copy for parallel execution)
67
+
68
+ Returns:
69
+ A Future containing the NodeResult
70
+ """
71
+ from ice.enums import RunState
72
+
73
+ def execute() -> NodeResult:
74
+ try:
75
+ state = node.process(ctx)
76
+ return NodeResult(state=state)
77
+ except Exception as e:
78
+ return NodeResult(state=RunState.SHUT_DOWN, error=e)
79
+
80
+ executor = get_executor()
81
+ return executor.submit(execute)
82
+
@@ -0,0 +1,51 @@
1
+ """Time utilities for Ice SDK."""
2
+
3
+ from ice.enums import TimeType
4
+
5
+
6
+ def time_disabled(
7
+ time_type: TimeType | int,
8
+ request_time: int,
9
+ start: int,
10
+ end: int,
11
+ ) -> bool:
12
+ """
13
+ Check if the node should be disabled based on time constraints.
14
+
15
+ Args:
16
+ time_type: Time control type
17
+ request_time: Current request time in milliseconds
18
+ start: Start time in milliseconds
19
+ end: End time in milliseconds
20
+
21
+ Returns:
22
+ True if the node should be disabled, False otherwise
23
+ """
24
+ if isinstance(time_type, int):
25
+ time_type = TimeType(time_type) if time_type in TimeType._value2member_map_ else TimeType.NONE
26
+
27
+ if time_type == TimeType.NONE:
28
+ return False
29
+
30
+ if time_type == TimeType.BETWEEN:
31
+ # Must be between start and end
32
+ if start > 0 and request_time < start:
33
+ return True
34
+ if end > 0 and request_time > end:
35
+ return True
36
+ return False
37
+
38
+ if time_type == TimeType.AFTER_START:
39
+ # Must be after start
40
+ if start > 0 and request_time < start:
41
+ return True
42
+ return False
43
+
44
+ if time_type == TimeType.BEFORE_END:
45
+ # Must be before end
46
+ if end > 0 and request_time > end:
47
+ return True
48
+ return False
49
+
50
+ return False
51
+
@@ -0,0 +1,80 @@
1
+ """UUID utilities for Ice SDK."""
2
+
3
+ import base64
4
+ import secrets
5
+ import uuid as uuid_module
6
+
7
+ # 64 character alphabet (same as Java: A-Z, a-z, 0-9, -, _)
8
+ _DIGITS64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
9
+
10
+
11
+ def generate_uuid22() -> str:
12
+ """Generate a 22-character UUID (base64 encoded UUID without padding)."""
13
+ u = uuid_module.uuid4()
14
+ return base64.urlsafe_b64encode(u.bytes).decode("ascii").rstrip("=")
15
+
16
+
17
+ def generate_uuid() -> str:
18
+ """Generate a standard UUID string."""
19
+ return str(uuid_module.uuid4())
20
+
21
+
22
+ def generate_short_id() -> str:
23
+ """
24
+ Generate an 11-character short ID (same format as Java).
25
+ Uses 8 random bytes encoded with base64 variant to produce 11 characters.
26
+ """
27
+ random_bytes = bytearray(secrets.token_bytes(8))
28
+
29
+ # Set version 4 marker (same as Java)
30
+ random_bytes[6] = (random_bytes[6] & 0x0f) | 0x40
31
+
32
+ # Convert bytes to int64
33
+ msb = 0
34
+ for i in range(8):
35
+ msb = (msb << 8) | (random_bytes[i] & 0xff)
36
+
37
+ # Encode to 11 characters using base64 variant
38
+ out = [''] * 12
39
+ bit = 0
40
+ bt1 = 8
41
+ bt2 = 8
42
+ offsetm = 1
43
+ idx = 0
44
+
45
+ while offsetm > 0:
46
+ offsetm = 64 - ((bit + 3) << 3)
47
+
48
+ if bt1 > 3:
49
+ mask = (1 << (8 * 3)) - 1
50
+ elif bt1 >= 0:
51
+ mask = (1 << (8 * bt1)) - 1
52
+ bt2 -= 3 - bt1
53
+ else:
54
+ min_bt = min(bt2, 3)
55
+ mask = (1 << (8 * min_bt)) - 1
56
+ bt2 -= 3
57
+
58
+ tmp = 0
59
+ if bt1 > 0:
60
+ bt1 -= 3
61
+ if offsetm < 0:
62
+ tmp = msb
63
+ else:
64
+ tmp = (msb >> offsetm) & mask
65
+ if bt1 < 0:
66
+ tmp <<= abs(offsetm)
67
+
68
+ out[idx + 3] = _DIGITS64[tmp & 0x3f]
69
+ tmp >>= 6
70
+ out[idx + 2] = _DIGITS64[tmp & 0x3f]
71
+ tmp >>= 6
72
+ out[idx + 1] = _DIGITS64[tmp & 0x3f]
73
+ tmp >>= 6
74
+ out[idx] = _DIGITS64[tmp & 0x3f]
75
+
76
+ bit += 3
77
+ idx += 4
78
+
79
+ return ''.join(out[:11])
80
+
@@ -0,0 +1,6 @@
1
+ """Cache module for Ice SDK."""
2
+
3
+ from ice.cache import conf_cache
4
+ from ice.cache import handler_cache
5
+
6
+ __all__ = ["conf_cache", "handler_cache"]