init
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
"""Executes a pre-defined sequence of flow steps and records results."""
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Callable
|
||||
|
||||
import undetected_chromedriver as uc
|
||||
|
||||
from config import config
|
||||
from crawler.driver import human_delay
|
||||
|
||||
# A flow step is a callable that receives the driver and returns arbitrary data.
|
||||
StepFn = Callable[[uc.Chrome], dict[str, object]]
|
||||
|
||||
# Registry — add your custom step functions here.
|
||||
# Keys must match the step names in config.flow_steps.
|
||||
STEP_REGISTRY: dict[str, StepFn] = {}
|
||||
|
||||
|
||||
def register_step(name: str) -> Callable[[StepFn], StepFn]:
|
||||
"""Decorator to register a function as a named flow step."""
|
||||
def decorator(fn: StepFn) -> StepFn:
|
||||
STEP_REGISTRY[name] = fn
|
||||
return fn
|
||||
return decorator
|
||||
|
||||
|
||||
@dataclass
|
||||
class StepResult:
|
||||
name: str
|
||||
success: bool
|
||||
data: dict[str, object] = field(default_factory=dict)
|
||||
error: str | None = None
|
||||
duration_ms: int = 0
|
||||
|
||||
|
||||
@dataclass
|
||||
class FlowResult:
|
||||
scenario: str
|
||||
identifier: str # phone number or "fresh"
|
||||
steps: list[StepResult] = field(default_factory=list)
|
||||
|
||||
@property
|
||||
def success(self) -> bool:
|
||||
return all(s.success for s in self.steps)
|
||||
|
||||
@property
|
||||
def failure_reason(self) -> str | None:
|
||||
for s in self.steps:
|
||||
if not s.success:
|
||||
return f"{s.name}: {s.error}"
|
||||
return None
|
||||
|
||||
|
||||
class FlowRunner:
|
||||
def __init__(self, driver: uc.Chrome) -> None:
|
||||
self.driver = driver
|
||||
|
||||
def run(self, scenario: str, identifier: str) -> FlowResult:
|
||||
result = FlowResult(scenario=scenario, identifier=identifier)
|
||||
|
||||
for step_name in config.flow_steps:
|
||||
fn = STEP_REGISTRY.get(step_name)
|
||||
if fn is None:
|
||||
result.steps.append(
|
||||
StepResult(
|
||||
name=step_name,
|
||||
success=False,
|
||||
error=f"Step '{step_name}' not found in registry",
|
||||
)
|
||||
)
|
||||
break
|
||||
|
||||
t0 = time.monotonic()
|
||||
try:
|
||||
data = fn(self.driver)
|
||||
duration = int((time.monotonic() - t0) * 1000)
|
||||
result.steps.append(StepResult(name=step_name, success=True, data=data, duration_ms=duration))
|
||||
human_delay(0.5, 1.5)
|
||||
except Exception as exc:
|
||||
duration = int((time.monotonic() - t0) * 1000)
|
||||
result.steps.append(
|
||||
StepResult(name=step_name, success=False, error=str(exc), duration_ms=duration)
|
||||
)
|
||||
break # abort remaining steps on first failure
|
||||
|
||||
return result
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Example steps — replace with your actual flow logic
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@register_step("step_home")
|
||||
def step_home(driver: uc.Chrome) -> dict[str, object]:
|
||||
return {"url": driver.current_url, "title": driver.title}
|
||||
|
||||
|
||||
@register_step("step_browse")
|
||||
def step_browse(driver: uc.Chrome) -> dict[str, object]:
|
||||
# Example: navigate to a listing page
|
||||
human_delay(1.0, 2.0)
|
||||
return {"url": driver.current_url}
|
||||
Reference in New Issue
Block a user