pipefy 0.0.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 (37) hide show
  1. pipefy-0.0.0/PKG-INFO +7 -0
  2. pipefy-0.0.0/pyproject.toml +26 -0
  3. pipefy-0.0.0/setup.cfg +4 -0
  4. pipefy-0.0.0/src/pipefy/__init__.py +8 -0
  5. pipefy-0.0.0/src/pipefy/context.py +71 -0
  6. pipefy-0.0.0/src/pipefy/engine/__init__.py +6 -0
  7. pipefy-0.0.0/src/pipefy/engine/decision_engine.py +116 -0
  8. pipefy-0.0.0/src/pipefy/engine/middleware_engine.py +42 -0
  9. pipefy-0.0.0/src/pipefy/engine/node_registry.py +15 -0
  10. pipefy-0.0.0/src/pipefy/engine/node_runner.py +75 -0
  11. pipefy-0.0.0/src/pipefy/engine/step_runner.py +90 -0
  12. pipefy-0.0.0/src/pipefy/error.py +10 -0
  13. pipefy-0.0.0/src/pipefy/middleware/__init__.py +5 -0
  14. pipefy-0.0.0/src/pipefy/middleware/error_report.py +31 -0
  15. pipefy-0.0.0/src/pipefy/middleware/logger.py +47 -0
  16. pipefy-0.0.0/src/pipefy/middleware/middleware.py +82 -0
  17. pipefy-0.0.0/src/pipefy/middleware/report_trace.py +81 -0
  18. pipefy-0.0.0/src/pipefy/middleware/validation.py +66 -0
  19. pipefy-0.0.0/src/pipefy/node.py +41 -0
  20. pipefy-0.0.0/src/pipefy/pipeline.py +66 -0
  21. pipefy-0.0.0/src/pipefy/report.py +268 -0
  22. pipefy-0.0.0/src/pipefy/sdk/__init__.py +3 -0
  23. pipefy-0.0.0/src/pipefy/sdk/html/page.py +14 -0
  24. pipefy-0.0.0/src/pipefy/sdk/html/tag.py +32 -0
  25. pipefy-0.0.0/src/pipefy/sdk/html/theme.py +83 -0
  26. pipefy-0.0.0/src/pipefy/sdk/html/themes/default.py +268 -0
  27. pipefy-0.0.0/src/pipefy/sdk/html/ui.py +135 -0
  28. pipefy-0.0.0/src/pipefy/sdk/html/ui_base.py +95 -0
  29. pipefy-0.0.0/src/pipefy/sdk/logger.py +68 -0
  30. pipefy-0.0.0/src/pipefy/sdk/reference.py +13 -0
  31. pipefy-0.0.0/src/pipefy/sdk/save.py +9 -0
  32. pipefy-0.0.0/src/pipefy/test_pipeline.py +137 -0
  33. pipefy-0.0.0/src/pipefy.egg-info/PKG-INFO +7 -0
  34. pipefy-0.0.0/src/pipefy.egg-info/SOURCES.txt +35 -0
  35. pipefy-0.0.0/src/pipefy.egg-info/dependency_links.txt +1 -0
  36. pipefy-0.0.0/src/pipefy.egg-info/requires.txt +1 -0
  37. pipefy-0.0.0/src/pipefy.egg-info/top_level.txt +1 -0
pipefy-0.0.0/PKG-INFO ADDED
@@ -0,0 +1,7 @@
1
+ Metadata-Version: 2.4
2
+ Name: pipefy
3
+ Version: 0.0.0
4
+ Summary: A lightweight pipeline framework with validation, middleware, logging and reports.
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: polars
@@ -0,0 +1,26 @@
1
+ [project]
2
+ name = "pipefy"
3
+ dynamic = ["version"]
4
+ description = "A lightweight pipeline framework with validation, middleware, logging and reports."
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ dependencies = [
8
+ "polars",
9
+ ]
10
+
11
+ [build-system]
12
+ requires = ["setuptools>=68", "wheel"]
13
+ build-backend = "setuptools.build_meta"
14
+
15
+ [tool.setuptools]
16
+ package-dir = {"" = "src"}
17
+
18
+ [tool.setuptools_scm]
19
+ version_scheme = "guess-next-dev"
20
+ local_scheme = "no-local-version"
21
+
22
+ [tool.setuptools.packages.find]
23
+ where = ["src"]
24
+
25
+ [tool.setuptools.package-data]
26
+ pipefy = ["py.typed"]
pipefy-0.0.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,8 @@
1
+ from .context import Context, ErrorContext
2
+ from .node import Node
3
+ from .pipeline import Pipeline
4
+ from .error import ErrorDecision
5
+ from .report import PipelineErrorReport, PipelineNodeReport, PipelineReport, PipelineRetryReport, PipelineStepReport
6
+ from .middleware.middleware import Middleware
7
+
8
+ from .sdk import Logger, saveHTML, saveJson, getReference
@@ -0,0 +1,71 @@
1
+ from pathlib import Path
2
+ from typing import Any
3
+ import polars as pl
4
+ from typing import Callable
5
+
6
+ from .sdk import logger
7
+
8
+ from .error import ErrorDecision
9
+
10
+ df_readers: dict[str, Callable[[Path], pl.DataFrame]] = {
11
+ ".xlsx": pl.read_excel,
12
+ ".xls": pl.read_excel,
13
+ ".csv": pl.read_csv,
14
+ ".parquet": pl.read_parquet,
15
+ }
16
+
17
+ class Context:
18
+ def __init__(self, log: logger.Logger):
19
+ self.log: logger.Logger = log
20
+ self.data: dict[str, Any] = {}
21
+
22
+ self.read_keys: set[str] = set()
23
+ self.written_keys: set[str] = set()
24
+
25
+ def begin_tracking(self) -> None:
26
+ self.read_keys = set()
27
+ self.written_keys = set()
28
+
29
+ def get(self, key: str) -> Any:
30
+ self.read_keys.add(key)
31
+
32
+ if key not in self.data:
33
+ raise KeyError(f"Context key not found: {key}")
34
+
35
+ return self.data[key]
36
+
37
+ def set(self, key: str, value: Any) -> None:
38
+ self.written_keys.add(key)
39
+ self.data[key] = value
40
+
41
+ def has(self, key: str) -> bool:
42
+ return key in self.data
43
+
44
+ class ErrorContext:
45
+ def __init__(self, ctx: Context, exc: Exception):
46
+ self.error = exc
47
+ self._ctx = ctx
48
+
49
+ def get(self, key: str):
50
+ return self._ctx.get(key)
51
+
52
+ def set(self, key: str, value):
53
+ return self._ctx.set(key, value)
54
+
55
+ def has(self, key: str):
56
+ return self._ctx.has(key)
57
+
58
+ def abort(self, reason: str | None = None):
59
+ return ErrorDecision("abort", reason or str(self.error))
60
+
61
+ def retry(self, max_retries: int = 1, reason: str | None = None):
62
+ return ErrorDecision("retry", reason or str(self.error), max_retries)
63
+
64
+ def continue_(self, reason: str | None = None):
65
+ return ErrorDecision("continue", reason or str(self.error))
66
+
67
+ def skip(self, reason: str | None = None):
68
+ return ErrorDecision("skip", reason or str(self.error))
69
+
70
+ def is_(self, error_type: type[Exception]) -> bool:
71
+ return isinstance(self.error, error_type)
@@ -0,0 +1,6 @@
1
+
2
+ from .middleware_engine import MiddlewareEngine
3
+ from .node_registry import NodeRegistry
4
+ from .decision_engine import DecisionEngine
5
+ from .step_runner import StepRunner
6
+ from .node_runner import NodeRunner
@@ -0,0 +1,116 @@
1
+ from typing import Callable
2
+
3
+ from ..context import Context
4
+ from ..error import ErrorDecision
5
+ from ..report import PipelineStepReport, PipelineNodeReport, PipelineRetryReport
6
+ from ..engine.middleware_engine import MiddlewareEngine
7
+
8
+ class DecisionEngine:
9
+ def __init__(self, middleware: MiddlewareEngine):
10
+ self.middleware = middleware
11
+
12
+ def finish_step(
13
+ self,
14
+ step_report: PipelineStepReport,
15
+ node_report: PipelineNodeReport,
16
+ success: bool,
17
+ fixed: bool | None = None,
18
+ ) -> bool:
19
+ step_report.success = success
20
+ step_report.fixed = fixed
21
+
22
+ if not success:
23
+ node_report.success = False
24
+
25
+ node_report.steps.append(step_report)
26
+ return success
27
+
28
+ def handle(
29
+ self,
30
+ ctx: Context,
31
+ action: Callable[[Context], None],
32
+ decision: ErrorDecision,
33
+ step_report: PipelineStepReport,
34
+ node_report: PipelineNodeReport,
35
+ ) -> bool:
36
+ step_report.decision = decision.action
37
+ step_report.decision_reason = decision.reason
38
+
39
+ handlers = {
40
+ "continue": self._continue,
41
+ "skip": self._skip,
42
+ "abort": self._abort,
43
+ "retry": self._retry,
44
+ }
45
+
46
+ handler = handlers.get(decision.action)
47
+
48
+ if handler is None:
49
+ step_report.decision_reason = (
50
+ step_report.decision_reason
51
+ or f"Unknown error decision: {decision.action}"
52
+ )
53
+ return self.finish_step(step_report, node_report, False, False)
54
+
55
+ return handler(ctx, action, decision, step_report, node_report)
56
+
57
+ def _continue(self, ctx, action, decision, step_report, node_report):
58
+ return self.finish_step(step_report, node_report, True, True)
59
+
60
+ def _skip(self, ctx, action, decision, step_report, node_report):
61
+ return self.finish_step(step_report, node_report, True, False)
62
+
63
+ def _abort(self, ctx, action, decision, step_report, node_report):
64
+ return self.finish_step(step_report, node_report, False, False)
65
+
66
+ def _retry(self, ctx, action, decision, step_report, node_report):
67
+ max_retries = decision.max_retries or 1
68
+
69
+ for attempt in range(1, max_retries + 1):
70
+ retry_report = PipelineRetryReport(
71
+ attempt=attempt,
72
+ success=True,
73
+ )
74
+
75
+ self.middleware.emit(
76
+ "beforeRetry",
77
+ ctx,
78
+ retry_report,
79
+ attempt,
80
+ max_retries,
81
+ action,
82
+ )
83
+
84
+ try:
85
+ action(ctx)
86
+
87
+ retry_report.success = True
88
+ step_report.retries.append(retry_report)
89
+
90
+ self.middleware.emit(
91
+ "afterRetry",
92
+ ctx,
93
+ retry_report,
94
+ attempt,
95
+ max_retries,
96
+ action,
97
+ )
98
+
99
+ return self.finish_step(step_report, node_report, True, True)
100
+
101
+ except Exception as retry_err:
102
+ retry_report.success = False
103
+
104
+ self.middleware.emit(
105
+ "onRetryError",
106
+ ctx,
107
+ retry_report,
108
+ retry_err,
109
+ attempt,
110
+ max_retries,
111
+ action,
112
+ )
113
+
114
+ step_report.retries.append(retry_report)
115
+
116
+ return self.finish_step(step_report, node_report, False, False)
@@ -0,0 +1,42 @@
1
+ from ..middleware.middleware import Middleware, MiddlewareEvent
2
+
3
+ class MiddlewareEngine:
4
+ def __init__(self, *mw: Middleware):
5
+ self.middlewares = mw
6
+ self.listeners: dict[str, list[Middleware]] = self._index_middlewares(self.middlewares)
7
+
8
+ def _index_middlewares(self, middlewares: tuple[Middleware, ...]):
9
+ listeners = {}
10
+
11
+ for mw in middlewares:
12
+ for event in mw.events:
13
+ listeners.setdefault(event, []).append(mw)
14
+
15
+ return listeners
16
+
17
+ def emit(self, hook: MiddlewareEvent, *args) -> None:
18
+ for mw in self._listeners(hook):
19
+ getattr(mw, hook)(*args)
20
+
21
+ def every(self, hook: MiddlewareEvent, *args) -> bool:
22
+ for mw in self._listeners(hook):
23
+ result = getattr(mw, hook)(*args)
24
+
25
+ if result is False:
26
+ return False
27
+
28
+ return True
29
+
30
+ def emit_transform(self, hook: MiddlewareEvent, value):
31
+ result = value
32
+
33
+ for mw in self._listeners(hook):
34
+ result = getattr(mw, hook)(result)
35
+
36
+ if result is None:
37
+ raise TypeError(f"{mw.__class__.__name__}.{hook} must return a value")
38
+
39
+ return result
40
+
41
+ def _listeners(self, hook: MiddlewareEvent):
42
+ return self.listeners.get(hook, ())
@@ -0,0 +1,15 @@
1
+ from typing import Self
2
+
3
+ from ..node import Node
4
+ from .middleware_engine import MiddlewareEngine
5
+
6
+ class NodeRegistry:
7
+ def __init__(self, middleware: MiddlewareEngine):
8
+ self.middleware = middleware
9
+ self.nodes: list[Node] = []
10
+
11
+ def push(self, n: Node) -> Self:
12
+ self.nodes.append(
13
+ self.middleware.emit_transform("onPush", n)
14
+ )
15
+ return self
@@ -0,0 +1,75 @@
1
+ from ..context import Context
2
+ from ..node import Node
3
+ from ..report import PipelineNodeReport
4
+ from .step_runner import StepRunner
5
+ from .middleware_engine import MiddlewareEngine
6
+
7
+
8
+ class NodeRunner:
9
+ def __init__(
10
+ self,
11
+ middleware: MiddlewareEngine,
12
+ steps: StepRunner,
13
+ ):
14
+ self.middleware = middleware
15
+ self.steps = steps
16
+
17
+ def run(
18
+ self,
19
+ ctx: Context,
20
+ current_node: Node,
21
+ node_report: PipelineNodeReport,
22
+ ) -> bool:
23
+ self.middleware.emit("beforeRunNode", ctx, node_report, current_node)
24
+
25
+ prev_keys = set(ctx.data.keys())
26
+
27
+ try:
28
+ if not self.middleware.every(
29
+ "validateInputs",
30
+ ctx,
31
+ current_node,
32
+ node_report,
33
+ ):
34
+ return False
35
+
36
+ if not self.steps.run(
37
+ ctx,
38
+ "onPreRun",
39
+ current_node.onPreRun,
40
+ current_node.onPreRunErr,
41
+ node_report,
42
+ ):
43
+ return False
44
+
45
+ if not self.steps.run(
46
+ ctx,
47
+ "onRun",
48
+ current_node.onRun,
49
+ current_node.onRunErr,
50
+ node_report,
51
+ ):
52
+ return False
53
+
54
+ if not self.steps.run(
55
+ ctx,
56
+ "onPostRun",
57
+ current_node.onPostRun,
58
+ current_node.onPostRunErr,
59
+ node_report,
60
+ ):
61
+ return False
62
+
63
+ if not self.middleware.every(
64
+ "validateOutputs",
65
+ ctx,
66
+ current_node,
67
+ node_report,
68
+ prev_keys,
69
+ ):
70
+ return False
71
+
72
+ return True
73
+
74
+ finally:
75
+ self.middleware.emit("afterRunNode", ctx, node_report, current_node)
@@ -0,0 +1,90 @@
1
+ from typing import Callable
2
+
3
+ from ..context import Context, ErrorContext
4
+ from ..error import ErrorDecision
5
+ from ..report import PipelineNodeReport, PipelineStepReport
6
+ from ..sdk import reference
7
+
8
+ from .decision_engine import DecisionEngine
9
+ from .middleware_engine import MiddlewareEngine
10
+
11
+ class StepRunner:
12
+ def __init__(
13
+ self,
14
+ middleware: MiddlewareEngine,
15
+ decisions: DecisionEngine,
16
+ ):
17
+ self.middleware = middleware
18
+ self.decisions = decisions
19
+
20
+ def run(
21
+ self,
22
+ ctx: Context,
23
+ name: str,
24
+ action: Callable[[Context], None],
25
+ error_handler: Callable[[ErrorContext], ErrorDecision],
26
+ node_report: PipelineNodeReport,
27
+ ) -> bool:
28
+ step_report = PipelineStepReport(
29
+ name=name,
30
+ reference=reference.getReference(action),
31
+ success=True,
32
+ )
33
+
34
+ self.middleware.emit("beforeRunStep", ctx, step_report, name, action)
35
+
36
+ try:
37
+ action(ctx)
38
+
39
+ result = self.decisions.finish_step(
40
+ step_report,
41
+ node_report,
42
+ True,
43
+ )
44
+
45
+ self.middleware.emit("afterRunStep", ctx, step_report, name, action)
46
+ return result
47
+
48
+ except Exception as err:
49
+ err_ctx = ErrorContext(ctx, err)
50
+
51
+ self.middleware.emit("onStepError", ctx, step_report, err, error_handler)
52
+
53
+ try:
54
+ decision = error_handler(err_ctx)
55
+
56
+ if decision is None:
57
+ decision = err_ctx.abort(f"{name} must return an ErrorDecision")
58
+
59
+ except Exception as handler_err:
60
+ self.middleware.emit(
61
+ "onStepError",
62
+ ctx,
63
+ step_report,
64
+ handler_err,
65
+ error_handler,
66
+ )
67
+
68
+ step_report.decision = "abort"
69
+ step_report.decision_reason = "error handler failed"
70
+
71
+ result = self.decisions.finish_step(
72
+ step_report,
73
+ node_report,
74
+ False,
75
+ False,
76
+ )
77
+
78
+ self.middleware.emit("afterRunStep", ctx, step_report, name, action)
79
+ return result
80
+
81
+ result = self.decisions.handle(
82
+ ctx=ctx,
83
+ action=action,
84
+ decision=decision,
85
+ step_report=step_report,
86
+ node_report=node_report,
87
+ )
88
+
89
+ self.middleware.emit("afterRunStep", ctx, step_report, name, action)
90
+ return result
@@ -0,0 +1,10 @@
1
+ from dataclasses import dataclass
2
+ from typing import Literal
3
+
4
+ ErrorAction = Literal["abort", "retry", "skip", "continue"]
5
+
6
+ @dataclass
7
+ class ErrorDecision:
8
+ action: ErrorAction
9
+ reason: str | None = None
10
+ max_retries: int = 0
@@ -0,0 +1,5 @@
1
+ from .middleware import Middleware
2
+ from .error_report import ErrorReportMiddleware
3
+ from .logger import LoggerMiddleware
4
+ from .report_trace import ReportTraceMiddleware
5
+ from .validation import ValidationMiddleware
@@ -0,0 +1,31 @@
1
+ from ..report import PipelineErrorReport
2
+ from ..sdk import getReference
3
+ from .middleware import Middleware
4
+ import traceback
5
+
6
+ class ErrorReportMiddleware(Middleware):
7
+ events = (
8
+ "onStepError",
9
+ "onRetryError",
10
+ )
11
+ def build_error_report(self, err: Exception) -> PipelineErrorReport:
12
+ cause = err.__cause__ or err.__context__
13
+
14
+ return PipelineErrorReport(
15
+ type=type(err).__name__,
16
+ message=str(err),
17
+ traceback="".join(
18
+ traceback.format_exception(type(err), err, err.__traceback__)
19
+ ),
20
+ cause_type=type(cause).__name__ if cause else None,
21
+ cause_message=str(cause) if cause else None,
22
+ )
23
+
24
+ def onStepError(self, ctx, r, err, error_handler):
25
+ r.success = False
26
+ r.error = self.build_error_report(err)
27
+ r.handler_reference = getReference(error_handler)
28
+
29
+ def onRetryError(self, ctx, r, err, attempt, max_retries, action):
30
+ r.success = False
31
+ r.error = self.build_error_report(err)
@@ -0,0 +1,47 @@
1
+ from ..sdk import getReference
2
+ from .middleware import Middleware
3
+
4
+ class LoggerMiddleware(Middleware):
5
+ events = (
6
+ "beforeRunPipeline",
7
+ "afterRunPipeline",
8
+ "beforeRunNode",
9
+ "afterRunNode",
10
+ "beforeRunStep",
11
+ "afterRunStep",
12
+ "onStepError",
13
+ "beforeRetry",
14
+ "afterRetry",
15
+ "onRetryError",
16
+ )
17
+
18
+ def beforeRunPipeline(self, ctx, r):
19
+ ctx.log.info("pipeline", "started")
20
+
21
+ def afterRunPipeline(self, ctx, r):
22
+ ctx.log.info("pipeline", "success" if r.success else "failed")
23
+
24
+ def beforeRunNode(self, ctx, r, n):
25
+ ctx.log.info(n.id, "started")
26
+
27
+ def afterRunNode(self, ctx, r, n):
28
+ ctx.log.info(n.id, "success" if r.success else "failed")
29
+
30
+ def beforeRunStep(self, ctx, r, name, action):
31
+ ctx.log.info(getReference(action), "started")
32
+
33
+ def afterRunStep(self, ctx, r, name, action):
34
+ ctx.log.info(getReference(action), "success" if r.success else "failed")
35
+
36
+ def onStepError(self, ctx, r, err, error_handler):
37
+ ctx.log.error(r.reference, type(err).__name__, str(err))
38
+
39
+ def beforeRetry(self, ctx, r, attempt, max_retries, action):
40
+ ctx.log.info(getReference(action), f"retry {attempt}/{max_retries}")
41
+
42
+ def afterRetry(self, ctx, r, attempt, max_retries, action):
43
+ ctx.log.info(getReference(action), f"retry {attempt}/{max_retries} success")
44
+
45
+ def onRetryError(self, ctx, r, err, attempt, max_retries, action):
46
+ ctx.log.error(getReference(action), f"retry {attempt}/{max_retries} failed:", err)
47
+
@@ -0,0 +1,82 @@
1
+ from abc import ABC
2
+ from typing import Callable, Literal
3
+
4
+ from ..context import Context
5
+ from ..node import Node
6
+ from ..report import PipelineNodeReport, PipelineReport, PipelineRetryReport, PipelineStepReport
7
+
8
+ MiddlewareEvent = Literal[
9
+ "onPush",
10
+ "beforeRunPipeline",
11
+ "afterRunPipeline",
12
+ "beforeRunNode",
13
+ "afterRunNode",
14
+ "beforeRunStep",
15
+ "afterRunStep",
16
+ "onStepError",
17
+ "validateInputs",
18
+ "validateOutputs",
19
+ "beforeRetry",
20
+ "afterRetry",
21
+ "onRetryError",
22
+ ]
23
+
24
+ class Middleware(ABC):
25
+ events: tuple[MiddlewareEvent, ...] = (
26
+ "onPush",
27
+ "beforeRunPipeline",
28
+ "afterRunPipeline",
29
+ "beforeRunNode",
30
+ "afterRunNode",
31
+ "beforeRunStep",
32
+ "afterRunStep",
33
+ "onStepError",
34
+ "validateInputs",
35
+ "validateOutputs",
36
+ "beforeRetry",
37
+ "afterRetry",
38
+ "onRetryError",
39
+ )
40
+
41
+ def listens(self, event: MiddlewareEvent) -> bool:
42
+ return event in self.events
43
+
44
+ def onPush(self, n: Node) -> Node:
45
+ return n
46
+
47
+ def beforeRunPipeline(self, ctx: Context, r: PipelineReport) -> None:
48
+ pass
49
+
50
+ def afterRunPipeline(self, ctx: Context, r: PipelineReport) -> None:
51
+ pass
52
+
53
+ def beforeRunNode(self, ctx: Context, r: PipelineNodeReport, n: Node) -> None:
54
+ pass
55
+
56
+ def afterRunNode(self, ctx: Context, r: PipelineNodeReport, n: Node) -> None:
57
+ pass
58
+
59
+ def beforeRunStep(self, ctx: Context, r: PipelineStepReport, name: str, action: Callable) -> None:
60
+ pass
61
+
62
+ def afterRunStep(self, ctx: Context, r: PipelineStepReport, name: str, action: Callable) -> None:
63
+ pass
64
+
65
+ def onStepError(self, ctx: Context, r: PipelineStepReport, err: Exception, error_handler: Callable) -> None:
66
+ pass
67
+
68
+ def validateInputs(self, ctx: Context, n: Node, r: PipelineNodeReport) -> bool:
69
+ return True
70
+
71
+ def validateOutputs(self, ctx: Context, n: Node, r: PipelineNodeReport, prev_keys: set[str]) -> bool:
72
+ return True
73
+
74
+ def beforeRetry(self, ctx: Context, r: PipelineRetryReport, attempt: int, max_retries: int, action: Callable) -> None:
75
+ pass
76
+
77
+ def afterRetry(self, ctx: Context, r: PipelineRetryReport, attempt: int, max_retries: int, action: Callable) -> None:
78
+ pass
79
+
80
+ def onRetryError(self, ctx: Context, r: PipelineRetryReport, err: Exception, attempt: int, max_retries: int, action: Callable) -> None:
81
+ pass
82
+