logic-fingerprint 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. logic_fingerprint-0.1.0/PKG-INFO +178 -0
  2. logic_fingerprint-0.1.0/README.md +164 -0
  3. logic_fingerprint-0.1.0/pyproject.toml +38 -0
  4. logic_fingerprint-0.1.0/setup.cfg +4 -0
  5. logic_fingerprint-0.1.0/src/logic_fingerprint/__init__.py +2 -0
  6. logic_fingerprint-0.1.0/src/logic_fingerprint/api.py +86 -0
  7. logic_fingerprint-0.1.0/src/logic_fingerprint/app_factory.py +13 -0
  8. logic_fingerprint-0.1.0/src/logic_fingerprint/config.py +9 -0
  9. logic_fingerprint-0.1.0/src/logic_fingerprint/consensus.py +11 -0
  10. logic_fingerprint-0.1.0/src/logic_fingerprint/context_builder.py +21 -0
  11. logic_fingerprint-0.1.0/src/logic_fingerprint/errors.py +49 -0
  12. logic_fingerprint-0.1.0/src/logic_fingerprint/executor.py +52 -0
  13. logic_fingerprint-0.1.0/src/logic_fingerprint/fsm.py +77 -0
  14. logic_fingerprint-0.1.0/src/logic_fingerprint/handlers.py +26 -0
  15. logic_fingerprint-0.1.0/src/logic_fingerprint/heartbeat.py +10 -0
  16. logic_fingerprint-0.1.0/src/logic_fingerprint/input_models.py +4 -0
  17. logic_fingerprint-0.1.0/src/logic_fingerprint/lifespan.py +21 -0
  18. logic_fingerprint-0.1.0/src/logic_fingerprint/metrics.py +15 -0
  19. logic_fingerprint-0.1.0/src/logic_fingerprint/middleware.py +65 -0
  20. logic_fingerprint-0.1.0/src/logic_fingerprint/models.py +57 -0
  21. logic_fingerprint-0.1.0/src/logic_fingerprint/output_models.py +4 -0
  22. logic_fingerprint-0.1.0/src/logic_fingerprint/prometheus_metrics.py +26 -0
  23. logic_fingerprint-0.1.0/src/logic_fingerprint/protect.py +269 -0
  24. logic_fingerprint-0.1.0/src/logic_fingerprint/redis_consensus.py +48 -0
  25. logic_fingerprint-0.1.0/src/logic_fingerprint/runtime.py +49 -0
  26. logic_fingerprint-0.1.0/src/logic_fingerprint/service.py +12 -0
  27. logic_fingerprint-0.1.0/src/logic_fingerprint/validator.py +20 -0
  28. logic_fingerprint-0.1.0/src/logic_fingerprint.egg-info/PKG-INFO +178 -0
  29. logic_fingerprint-0.1.0/src/logic_fingerprint.egg-info/SOURCES.txt +38 -0
  30. logic_fingerprint-0.1.0/src/logic_fingerprint.egg-info/dependency_links.txt +1 -0
  31. logic_fingerprint-0.1.0/src/logic_fingerprint.egg-info/requires.txt +1 -0
  32. logic_fingerprint-0.1.0/src/logic_fingerprint.egg-info/top_level.txt +1 -0
  33. logic_fingerprint-0.1.0/tests/test_api.py +29 -0
  34. logic_fingerprint-0.1.0/tests/test_context_builder.py +17 -0
  35. logic_fingerprint-0.1.0/tests/test_executor.py +38 -0
  36. logic_fingerprint-0.1.0/tests/test_fsm.py +28 -0
  37. logic_fingerprint-0.1.0/tests/test_middleware.py +35 -0
  38. logic_fingerprint-0.1.0/tests/test_prometheus_metrics.py +20 -0
  39. logic_fingerprint-0.1.0/tests/test_redis_ttl_backend.py +36 -0
  40. logic_fingerprint-0.1.0/tests/test_runtime.py +8 -0
@@ -0,0 +1,178 @@
1
+ Metadata-Version: 2.4
2
+ Name: logic-fingerprint
3
+ Version: 0.1.0
4
+ Summary: Execution safety layer for functions, APIs, and LLMs.
5
+ Author: Your Name
6
+ License: MIT
7
+ Keywords: llm,circuit-breaker,middleware,reliability,api,decorator
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.9
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: pydantic>=1.10
14
+
15
+
16
+ Add a safety layer to any function.
17
+
18
+ Logic Fingerprint protects your function/LLM/API calls with:
19
+
20
+ circuit breaker (no retry storms)
21
+ safe recovery (HALF_OPEN probing)
22
+ schema validation (stable outputs)
23
+ unified error handling
24
+
25
+ Works with a single decorator: @protect()
26
+
27
+ ## 🧭 How it works
28
+
29
+ ```text
30
+ Your Function
31
+
32
+ @protect
33
+
34
+ Logic Fingerprint
35
+ ├─ Circuit Breaker
36
+ ├─ Probe Recovery
37
+ ├─ Validation Layer
38
+ ├─ Error Control
39
+
40
+ Safe Execution
41
+
42
+ Result / Error
43
+ ```
44
+
45
+ ## ⚡ Quick Start
46
+
47
+ ### Install
48
+
49
+ ```bash
50
+ pip install -r requirements.txt
51
+ pip install -e .
52
+ ```
53
+
54
+ ### Protect a real local LLM call
55
+
56
+ ```python
57
+ from logic_fingerprint import protect
58
+
59
+
60
+ @protect()
61
+ def ask_local_llm(request):
62
+ import json
63
+ import urllib.request
64
+
65
+ payload = {
66
+ "model": "llama3.2",
67
+ "prompt": request.payload["prompt"],
68
+ "stream": False,
69
+ }
70
+
71
+ req = urllib.request.Request(
72
+ "http://127.0.0.1:11434/api/generate",
73
+ data=json.dumps(payload).encode("utf-8"),
74
+ headers={"Content-Type": "application/json"},
75
+ method="POST",
76
+ )
77
+
78
+ with urllib.request.urlopen(req, timeout=60) as resp:
79
+ body = json.loads(resp.read().decode("utf-8"))
80
+
81
+ return {"answer": body["response"].strip()}
82
+ ```
83
+
84
+ ### Call it like a normal function
85
+
86
+ ```python
87
+ result = ask_local_llm({
88
+ "prompt": "Explain circuit breaker in one sentence."
89
+ })
90
+
91
+ print(result)
92
+ ```
93
+
94
+ Output:
95
+
96
+ ```python
97
+ {'answer': 'A circuit breaker is a safety device that automatically stops an overcurrent flow in an electrical circuit to prevent damage to equipment and potential hazards.'}
98
+ ```
99
+
100
+ ### What you get automatically
101
+
102
+ * circuit breaker protection
103
+ * safe recovery probing
104
+ * controlled failure handling
105
+ * optional schema validation
106
+ * auto request context
107
+
108
+
109
+
110
+ ```md
111
+ ## 🎬 Demos
112
+
113
+ Run real examples locally:
114
+
115
+ ### 1. Simple mode (like a normal function)
116
+
117
+ ```bash
118
+ python demo/demo_protect_simple.py
119
+ ```
120
+
121
+ ---
122
+
123
+ ### 2. Full mode (engineering output)
124
+
125
+ ```bash
126
+ python demo/demo_protect_full.py
127
+ ```
128
+
129
+ ---
130
+
131
+ ### 3. Error handling (simple mode)
132
+
133
+ ```bash
134
+ python demo/demo_error_simple.py
135
+ ```
136
+
137
+ ---
138
+
139
+ ### 4. Error handling (full mode)
140
+
141
+ ```bash
142
+ python demo/demo_error_full.py
143
+ ```
144
+
145
+ ---
146
+
147
+ ### 5. Real LLM demo (Ollama)
148
+
149
+ ```bash
150
+ python demo/demo_protect_ollama_simple.py
151
+ ```
152
+
153
+ 👉 Make sure Ollama is running locally before running this demo.
154
+
155
+ ------------------------------------------------------------------------
156
+
157
+ **Logic Fingerprint — Execution Safety Layer (Python)**
158
+
159
+ * Designed and implemented a decorator-based execution control layer for functions, APIs, and LLM calls
160
+ * Built circuit breaker with HALF_OPEN recovery, time-driven probing, and consecutive-success gating
161
+ * Added schema validation (Pydantic) and unified error protocol for stable, observable outputs
162
+ * Delivered dual-mode API (`simple` vs `full`) to support both developer-friendly usage and production observability
163
+ * Integrated with local LLM (Ollama) to demonstrate real-world stability against timeouts and malformed outputs
164
+ * Structured demos and documentation to enable 30-second onboarding and clear behavior comparison
165
+
166
+
167
+ Logic Fingerprint — 执行安全层(Python)
168
+
169
+ 设计并实现基于装饰器的执行控制层,用于函数 / API / LLM 调用的稳定性保护
170
+ 实现熔断机制(CLOSED / OPEN / HALF_OPEN)及时间驱动探测恢复与连续成功判定
171
+ 引入输入输出 Schema 校验(Pydantic)与统一错误协议,保证结果结构稳定、可观测
172
+ 设计双模式接口(simple / full),兼顾易用性与工程可观测性
173
+ 集成本地 LLM(Ollama)进行真实场景验证,解决 timeout、异常输出等问题
174
+ 构建分层 demo 与文档体系,实现 30 秒上手与行为对照演示
175
+
176
+ ---
177
+ “I built a decorator-based execution safety layer for LLM and API calls.”
178
+
@@ -0,0 +1,164 @@
1
+
2
+ Add a safety layer to any function.
3
+
4
+ Logic Fingerprint protects your function/LLM/API calls with:
5
+
6
+ circuit breaker (no retry storms)
7
+ safe recovery (HALF_OPEN probing)
8
+ schema validation (stable outputs)
9
+ unified error handling
10
+
11
+ Works with a single decorator: @protect()
12
+
13
+ ## 🧭 How it works
14
+
15
+ ```text
16
+ Your Function
17
+
18
+ @protect
19
+
20
+ Logic Fingerprint
21
+ ├─ Circuit Breaker
22
+ ├─ Probe Recovery
23
+ ├─ Validation Layer
24
+ ├─ Error Control
25
+
26
+ Safe Execution
27
+
28
+ Result / Error
29
+ ```
30
+
31
+ ## ⚡ Quick Start
32
+
33
+ ### Install
34
+
35
+ ```bash
36
+ pip install -r requirements.txt
37
+ pip install -e .
38
+ ```
39
+
40
+ ### Protect a real local LLM call
41
+
42
+ ```python
43
+ from logic_fingerprint import protect
44
+
45
+
46
+ @protect()
47
+ def ask_local_llm(request):
48
+ import json
49
+ import urllib.request
50
+
51
+ payload = {
52
+ "model": "llama3.2",
53
+ "prompt": request.payload["prompt"],
54
+ "stream": False,
55
+ }
56
+
57
+ req = urllib.request.Request(
58
+ "http://127.0.0.1:11434/api/generate",
59
+ data=json.dumps(payload).encode("utf-8"),
60
+ headers={"Content-Type": "application/json"},
61
+ method="POST",
62
+ )
63
+
64
+ with urllib.request.urlopen(req, timeout=60) as resp:
65
+ body = json.loads(resp.read().decode("utf-8"))
66
+
67
+ return {"answer": body["response"].strip()}
68
+ ```
69
+
70
+ ### Call it like a normal function
71
+
72
+ ```python
73
+ result = ask_local_llm({
74
+ "prompt": "Explain circuit breaker in one sentence."
75
+ })
76
+
77
+ print(result)
78
+ ```
79
+
80
+ Output:
81
+
82
+ ```python
83
+ {'answer': 'A circuit breaker is a safety device that automatically stops an overcurrent flow in an electrical circuit to prevent damage to equipment and potential hazards.'}
84
+ ```
85
+
86
+ ### What you get automatically
87
+
88
+ * circuit breaker protection
89
+ * safe recovery probing
90
+ * controlled failure handling
91
+ * optional schema validation
92
+ * auto request context
93
+
94
+
95
+
96
+ ```md
97
+ ## 🎬 Demos
98
+
99
+ Run real examples locally:
100
+
101
+ ### 1. Simple mode (like a normal function)
102
+
103
+ ```bash
104
+ python demo/demo_protect_simple.py
105
+ ```
106
+
107
+ ---
108
+
109
+ ### 2. Full mode (engineering output)
110
+
111
+ ```bash
112
+ python demo/demo_protect_full.py
113
+ ```
114
+
115
+ ---
116
+
117
+ ### 3. Error handling (simple mode)
118
+
119
+ ```bash
120
+ python demo/demo_error_simple.py
121
+ ```
122
+
123
+ ---
124
+
125
+ ### 4. Error handling (full mode)
126
+
127
+ ```bash
128
+ python demo/demo_error_full.py
129
+ ```
130
+
131
+ ---
132
+
133
+ ### 5. Real LLM demo (Ollama)
134
+
135
+ ```bash
136
+ python demo/demo_protect_ollama_simple.py
137
+ ```
138
+
139
+ 👉 Make sure Ollama is running locally before running this demo.
140
+
141
+ ------------------------------------------------------------------------
142
+
143
+ **Logic Fingerprint — Execution Safety Layer (Python)**
144
+
145
+ * Designed and implemented a decorator-based execution control layer for functions, APIs, and LLM calls
146
+ * Built circuit breaker with HALF_OPEN recovery, time-driven probing, and consecutive-success gating
147
+ * Added schema validation (Pydantic) and unified error protocol for stable, observable outputs
148
+ * Delivered dual-mode API (`simple` vs `full`) to support both developer-friendly usage and production observability
149
+ * Integrated with local LLM (Ollama) to demonstrate real-world stability against timeouts and malformed outputs
150
+ * Structured demos and documentation to enable 30-second onboarding and clear behavior comparison
151
+
152
+
153
+ Logic Fingerprint — 执行安全层(Python)
154
+
155
+ 设计并实现基于装饰器的执行控制层,用于函数 / API / LLM 调用的稳定性保护
156
+ 实现熔断机制(CLOSED / OPEN / HALF_OPEN)及时间驱动探测恢复与连续成功判定
157
+ 引入输入输出 Schema 校验(Pydantic)与统一错误协议,保证结果结构稳定、可观测
158
+ 设计双模式接口(simple / full),兼顾易用性与工程可观测性
159
+ 集成本地 LLM(Ollama)进行真实场景验证,解决 timeout、异常输出等问题
160
+ 构建分层 demo 与文档体系,实现 30 秒上手与行为对照演示
161
+
162
+ ---
163
+ “I built a decorator-based execution safety layer for LLM and API calls.”
164
+
@@ -0,0 +1,38 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "logic-fingerprint"
7
+ version = "0.1.0"
8
+ description = "Execution safety layer for functions, APIs, and LLMs."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+
12
+ authors = [
13
+ { name = "Your Name" }
14
+ ]
15
+
16
+ license = { text = "MIT" }
17
+
18
+ dependencies = [
19
+ "pydantic>=1.10"
20
+ ]
21
+
22
+ keywords = [
23
+ "llm",
24
+ "circuit-breaker",
25
+ "middleware",
26
+ "reliability",
27
+ "api",
28
+ "decorator"
29
+ ]
30
+
31
+ classifiers = [
32
+ "Programming Language :: Python :: 3",
33
+ "License :: OSI Approved :: MIT License",
34
+ "Operating System :: OS Independent"
35
+ ]
36
+
37
+ [tool.setuptools.packages.find]
38
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,2 @@
1
+ from .runtime import build_runtime
2
+ from .protect import protect, create_protector
@@ -0,0 +1,86 @@
1
+ from dataclasses import asdict, is_dataclass
2
+ from fastapi import APIRouter, Response
3
+ from pydantic import BaseModel, Field
4
+ from .models import HandlerRequest, RequestContext
5
+ from .prometheus_metrics import render_prometheus_metrics
6
+
7
+ class ForceFailRequest(BaseModel):
8
+ reason: str = "MANUAL_FAIL"
9
+
10
+ class ExecuteHandlerContextModel(BaseModel):
11
+ request_id: str | None = None
12
+ trace_id: str | None = None
13
+ user_id: str | None = None
14
+ source: str | None = None
15
+ timestamp: str | None = None
16
+ headers: dict = Field(default_factory=dict)
17
+ metadata: dict = Field(default_factory=dict)
18
+
19
+ class ExecuteHandlerRequest(BaseModel):
20
+ handler: str
21
+ payload: dict = Field(default_factory=dict)
22
+ context: ExecuteHandlerContextModel = Field(default_factory=ExecuteHandlerContextModel)
23
+ now: float | None = None
24
+
25
+ def build_router(runtime: object) -> APIRouter:
26
+ router = APIRouter()
27
+ fsm = runtime.fsm
28
+ metrics = runtime.metrics
29
+
30
+ @router.get("/")
31
+ def root():
32
+ return {"service": "logic-fingerprint", "state": fsm.state.value, "docs": "/docs"}
33
+
34
+ @router.get("/favicon.ico")
35
+ def favicon():
36
+ return Response(status_code=204)
37
+
38
+ @router.get("/healthz")
39
+ def healthz():
40
+ return {"ok": True, "state": fsm.state.value}
41
+
42
+ @router.get("/handlers")
43
+ def handlers():
44
+ return {"handlers": runtime.handler_registry.names()}
45
+
46
+ @router.get("/metrics")
47
+ def metrics_view():
48
+ return metrics.snapshot()
49
+
50
+ @router.get("/metrics.prom")
51
+ def metrics_prom():
52
+ return Response(content=render_prometheus_metrics(metrics, fsm), media_type="text/plain; version=0.0.4")
53
+
54
+ @router.post("/force_fail")
55
+ def force_fail(payload: ForceFailRequest):
56
+ fsm.record_hard_fail(payload.reason)
57
+ return {"state": fsm.state.value, "reason": payload.reason}
58
+
59
+ @router.post("/move_half_open")
60
+ def move_half_open():
61
+ fsm.move_to_half_open()
62
+ return {"state": fsm.state.value}
63
+
64
+ @router.post("/execute_handler")
65
+ async def execute_handler(payload: ExecuteHandlerRequest):
66
+ request = HandlerRequest(
67
+ payload=payload.payload,
68
+ context=RequestContext(
69
+ request_id=payload.context.request_id,
70
+ trace_id=payload.context.trace_id,
71
+ user_id=payload.context.user_id,
72
+ source=payload.context.source,
73
+ timestamp=payload.context.timestamp,
74
+ headers=payload.context.headers,
75
+ metadata=payload.context.metadata,
76
+ ),
77
+ )
78
+ outcome = await runtime.middleware.execute_handler_async(payload.handler, request=request, now=payload.now)
79
+ if outcome.succeeded:
80
+ result = outcome.result
81
+ if is_dataclass(result):
82
+ result = asdict(result)
83
+ return {"ok": True, "result": result}
84
+ return {"ok": False, "error": {"code": outcome.error_code, "message": outcome.error_message, "details": outcome.error_details or {}}}
85
+
86
+ return router
@@ -0,0 +1,13 @@
1
+ from fastapi import FastAPI
2
+ from .api import build_router
3
+ from .lifespan import build_lifespan
4
+ from .runtime import build_runtime
5
+
6
+ def create_app() -> FastAPI:
7
+ runtime = build_runtime()
8
+ app = FastAPI(title="Logic Fingerprint System", version="1.0.0", lifespan=build_lifespan(runtime, interval_seconds=1.0))
9
+ app.state.runtime = runtime
10
+ app.include_router(build_router(runtime))
11
+ return app
12
+
13
+ app = create_app()
@@ -0,0 +1,9 @@
1
+ from dataclasses import dataclass
2
+
3
+ @dataclass(slots=True)
4
+ class ProbeConfig:
5
+ probe_rate: float = 0.1
6
+ probe_interval_seconds: float = 10.0
7
+ consecutive_success_threshold: int = 3
8
+ total_nodes: int = 1
9
+ global_fail_threshold: float = 1.0
@@ -0,0 +1,11 @@
1
+ class InMemoryConsensusBackend:
2
+ def __init__(self) -> None:
3
+ self._failed_nodes: set[str] = set()
4
+ def mark_failed(self, instance_id: str) -> None:
5
+ self._failed_nodes.add(instance_id)
6
+ def clear_failed(self, instance_id: str) -> None:
7
+ self._failed_nodes.discard(instance_id)
8
+ def fail_count(self) -> int:
9
+ return len(self._failed_nodes)
10
+ def is_failed(self, instance_id: str) -> bool:
11
+ return instance_id in self._failed_nodes
@@ -0,0 +1,21 @@
1
+ from datetime import datetime, timezone
2
+ from uuid import uuid4
3
+ from .models import HandlerRequest, RequestContext
4
+
5
+ class ContextBuilder:
6
+ def __init__(self, default_source: str = "api") -> None:
7
+ self.default_source = default_source
8
+ def build_context(self, context: RequestContext | None = None) -> RequestContext:
9
+ context = context or RequestContext()
10
+ return RequestContext(
11
+ request_id=context.request_id or f"req-{uuid4().hex}",
12
+ trace_id=context.trace_id or f"trace-{uuid4().hex}",
13
+ user_id=context.user_id,
14
+ source=context.source or self.default_source,
15
+ timestamp=context.timestamp or datetime.now(timezone.utc).isoformat(),
16
+ headers=dict(context.headers),
17
+ metadata=dict(context.metadata),
18
+ )
19
+ def build_request(self, request: HandlerRequest | None = None) -> HandlerRequest:
20
+ request = request or HandlerRequest()
21
+ return HandlerRequest(payload=dict(request.payload), context=self.build_context(request.context))
@@ -0,0 +1,49 @@
1
+ from enum import Enum
2
+
3
+ class ErrorCode(str, Enum):
4
+ ERR_TIMEOUT = "ERR_TIMEOUT"
5
+ ERR_NULL = "ERR_NULL"
6
+ ERR_NORM = "ERR_NORM"
7
+ ERR_LOGIC = "ERR_LOGIC"
8
+ ERR_EXECUTION_BLOCKED = "ERR_EXECUTION_BLOCKED"
9
+ ERR_UNKNOWN = "ERR_UNKNOWN"
10
+ ERR_HANDLER_NOT_FOUND = "ERR_HANDLER_NOT_FOUND"
11
+ ERR_VALIDATION = "ERR_VALIDATION"
12
+ ERR_OUTPUT_VALIDATION = "ERR_OUTPUT_VALIDATION"
13
+
14
+ class LogicFingerprintError(Exception):
15
+ code: ErrorCode = ErrorCode.ERR_UNKNOWN
16
+ def __init__(self, message: str = "", details: dict | None = None) -> None:
17
+ super().__init__(message)
18
+ self.message = message or self.__class__.__name__
19
+ self.details = details or {}
20
+
21
+ class TimeoutErrorLF(LogicFingerprintError):
22
+ code = ErrorCode.ERR_TIMEOUT
23
+
24
+ class NullResultError(LogicFingerprintError):
25
+ code = ErrorCode.ERR_NULL
26
+
27
+ class NormalizationError(LogicFingerprintError):
28
+ code = ErrorCode.ERR_NORM
29
+
30
+ class LogicExecutionError(LogicFingerprintError):
31
+ code = ErrorCode.ERR_LOGIC
32
+
33
+ class HandlerNotFoundError(LogicFingerprintError):
34
+ code = ErrorCode.ERR_HANDLER_NOT_FOUND
35
+
36
+ class ValidationErrorLF(LogicFingerprintError):
37
+ code = ErrorCode.ERR_VALIDATION
38
+
39
+ class OutputValidationErrorLF(LogicFingerprintError):
40
+ code = ErrorCode.ERR_OUTPUT_VALIDATION
41
+
42
+ def classify_exception(exc: Exception) -> ErrorCode:
43
+ if isinstance(exc, LogicFingerprintError):
44
+ return exc.code
45
+ if isinstance(exc, TimeoutError):
46
+ return ErrorCode.ERR_TIMEOUT
47
+ if isinstance(exc, ValueError):
48
+ return ErrorCode.ERR_NORM
49
+ return ErrorCode.ERR_UNKNOWN
@@ -0,0 +1,52 @@
1
+ import inspect
2
+ from dataclasses import dataclass
3
+ from .errors import ErrorCode, NullResultError, classify_exception, LogicFingerprintError
4
+ from .models import ExecutionDecision, ExecutionOutcome, FSMState, ProbeResult
5
+
6
+ @dataclass(slots=True)
7
+ class LogicFingerprintExecutor:
8
+ fsm: object
9
+
10
+ def _build_decision_from_closed(self):
11
+ info = self.fsm.before_request()
12
+ return ExecutionDecision(state=str(info["state"]), allow_request=bool(info["allow_request"]), allow_probe=bool(info["allow_probe"]), global_fail_ratio=float(info["global_fail_ratio"]), external_fail_ratio=float(info["external_fail_ratio"]), is_probe=False)
13
+ def _build_decision_from_half_open(self, now):
14
+ info = self.fsm.before_half_open_request(now=now)
15
+ return ExecutionDecision(state=str(info["state"]), allow_request=bool(info["allow_request"]), allow_probe=bool(info["allow_probe"]), global_fail_ratio=float(info["global_fail_ratio"]), external_fail_ratio=float(info["external_fail_ratio"]), is_probe=bool(info["allow_probe"]))
16
+ def _blocked_outcome(self, decision):
17
+ return ExecutionOutcome(decision=decision, executed=False, succeeded=False, state_after=self.fsm.state.value, error_code=ErrorCode.ERR_EXECUTION_BLOCKED.value, error_message="Request blocked by FSM state.")
18
+ def _success_outcome(self, decision, result):
19
+ if decision.is_probe:
20
+ self.fsm.evaluate_probe(ProbeResult(system_success=True, business_success=True))
21
+ return ExecutionOutcome(decision=decision, executed=True, succeeded=True, state_after=self.fsm.state.value, result=result)
22
+ def _failure_outcome(self, decision, exc):
23
+ code = classify_exception(exc)
24
+ details = exc.details if isinstance(exc, LogicFingerprintError) else {}
25
+ self.fsm.record_hard_fail(code.value)
26
+ return ExecutionOutcome(decision=decision, executed=True, succeeded=False, state_after=self.fsm.state.value, error_code=code.value, error_message=str(exc), error_details=details)
27
+ def execute(self, operation, now=None):
28
+ decision = self._build_decision_from_half_open(now) if self.fsm.state == FSMState.HALF_OPEN else self._build_decision_from_closed()
29
+ if not decision.allow_request and not decision.allow_probe:
30
+ return self._blocked_outcome(decision)
31
+ try:
32
+ result = operation()
33
+ if inspect.isawaitable(result):
34
+ raise TypeError("Async operation cannot be executed by sync execute(); use execute_async().")
35
+ if result is None:
36
+ raise NullResultError("Operation returned None.")
37
+ return self._success_outcome(decision, result)
38
+ except Exception as exc:
39
+ return self._failure_outcome(decision, exc)
40
+ async def execute_async(self, operation, now=None):
41
+ decision = self._build_decision_from_half_open(now) if self.fsm.state == FSMState.HALF_OPEN else self._build_decision_from_closed()
42
+ if not decision.allow_request and not decision.allow_probe:
43
+ return self._blocked_outcome(decision)
44
+ try:
45
+ result = operation()
46
+ if inspect.isawaitable(result):
47
+ result = await result
48
+ if result is None:
49
+ raise NullResultError("Operation returned None.")
50
+ return self._success_outcome(decision, result)
51
+ except Exception as exc:
52
+ return self._failure_outcome(decision, exc)