ddp_utils.file_scanner¶
import ddp_utils.file_scanner
Poll a directory for stable signal files and dispatch their handlers.
The scanner supports exact names and glob patterns, adaptive polling, file stability checks, retry policies, staged idle timeouts, health checks, and structured terminal results. Handler failures are recoverable unless their watch policy makes them fatal.
Examples
Stop after handling a completion signal:
from ddp_utils.file_scanner import FileScanner, WatchSpec, ScanDecision
scanner = FileScanner("downloads")
scanner.add_watch(WatchSpec("done.json", lambda: ScanDecision.STOP))
result = scanner.run(timeout_seconds=60)
- ddp_utils.file_scanner.normalize_file_name_pattern(pattern: str) str¶
Convert brace placeholders in a filename template to one glob star.
Parameters
Name
Type
Description
pattern
str
Filename template such as
"{GUID}_{CASE_NO}.json".Returns
Type
Description
str
Glob pattern with every
{...}placeholder replaced by*, spaces around stars removed, and consecutive stars collapsed. An empty result becomes"*".Raises
Exception
Description
ValueError
patternis not a string.Examples
Normalize a signal filename template:
pattern = normalize_file_name_pattern("{GUID}_{CASE_NO}") assert pattern == "*_*"
- class ddp_utils.file_scanner.ScanDecision(value)¶
Bases:
EnumSelect scanner behavior after a handler succeeds.
CONTINUEkeeps polling,STOPterminates normally, andDISABLEremoves the successful watch while leaving the scanner running.Examples
Stop the scanner from a completion handler:
return ScanDecision.STOP
- class ddp_utils.file_scanner.StopReason(value)¶
Bases:
EnumDescribe why a scan terminated without propagating an exception.
Values distinguish explicit stops, three timeout classes, user interrupt, fatal handler or scanner failure, and external health-check failure.
Examples
Branch on an idle timeout:
if result.reason is StopReason.IDLE_TIMEOUT: print("No signal arrived")
- class ddp_utils.file_scanner.ScanResult(reason: StopReason, message: str = '', data: Mapping[str, ~typing.Any]=<factory>)¶
Bases:
objectRepresent the terminal outcome of
FileScanner.run().Variables
Name
Type
Description
reason
Structured reason for termination.
message
str
Human-readable explanation.
data
Mapping[str, Any]
Additional immutable-by-contract context supplied by the scanner.
Examples
Construct an explicit successful stop result:
result = ScanResult(StopReason.STOP_REQUESTED, "completed")
- class ddp_utils.file_scanner.WatchSpec(filename: str, handler: Callable[[...], Any], decision_on_success: ScanDecision = ScanDecision.CONTINUE, remove_after_handled: bool = True, file_kind: str = 'generic', fatal_on_exception: bool = False, max_errors_before_fatal: int = 0, idle_timeout_after: int | None = None, max_retries: int = 0, retry_delay: float = 2.0)¶
Bases:
objectDescribe one exact-name or glob-pattern file watch.
Variables
Name
Type
Description
filename
str
Name, relative path, or glob pattern below
base_dir.handler
Callable[[...], Any]
Callable accepting no arguments, a file path, a
FileEvent, or both event and path.decision_on_success
Fallback decision when the handler returns no explicit decision.
remove_after_handled
bool
Delete a successfully handled file after the scanner’s configured delay.
file_kind
str
Diagnostic category and staged-timeout lookup key.
fatal_on_exception
bool
Convert handler failures into fatal termination.
max_errors_before_fatal
int
Number of errors tolerated before a fatal watch stops the scanner. Zero makes the first error fatal.
idle_timeout_after
int | None
Positive idle timeout applied after success.
max_retries
int
Per-watch retry limit; zero uses the scanner default.
retry_delay
float
Delay before the same failed file is retried.
Examples
Watch a completion file and stop after handling it:
spec = WatchSpec( filename="done.json", handler=lambda path: print(path), decision_on_success=ScanDecision.STOP, )
- class ddp_utils.file_scanner.Rule¶
Bases:
objectBuild and append
WatchSpecobjects to a target.Examples
Add a rule to a plain configuration holder:
Rule.add(holder, rule_data)
- static add(target, data: Dict[str, Any]) None¶
Append a validated watch definition to
target.watch_specs.Parameters
Name
Type
Description
target
Object whose
watch_specsattribute is a list. A missing attribute is created automatically.data
Dict[str, Any]
Rule mapping containing
file_kind, callablehandler, nameddecision_on_success, optional deletion and idle policies, and an already resolvedfilename.Raises
Exception
Description
ValueError
Required values are missing, invalid, or inconsistent.
AttributeError
Existing
target.watch_specsis not a list.Examples
Add a generated rule to a scanner configuration object:
Rule.add(holder, { "file_kind": "RESULT", "filename": "*_result.json", "handler": handle_result, "decision_on_success": "CONTINUE", })
- class ddp_utils.file_scanner.Template(directory: str | PathLike)¶
Bases:
objectGenerate scanner JSON and load validated watch specifications.
Handler strings may name a method on the issuer or use
"object.path:method"to resolve a nested handler.Examples
Prepare a template directory:
template = Template("configs")
Create the default template directory when necessary.
Parameters
Name
Type
Description
directory
Union[str, os.PathLike]
Default directory used by
generate()andload(). It is created recursively.Examples
Use a project-local configuration directory:
template = Template("lib/configs")
- generate(filename: str | None = None, output_path: str | PathLike | None = None, extension: bool = False) bool¶
Write the built-in example scanner configuration as JSON.
Parameters
Name
Type
Description
filename
str | None
Output filename. Defaults to
file_scanner_instructions_template.json.output_path
str | PathLike | None
Override for the directory supplied at construction.
extension
bool
Accepted for API compatibility; the current generated document is the same for either value.
Returns
Type
Description
bool
Trueafter a successful write;Falsewhen any write or serialization operation fails.Examples
Generate the default example file:
created = Template("configs").generate()
- load(issuer, file: str, guid: str, ext_path: str | None = None, ext_obj: bool = False) bool | dict | tuple¶
Load JSON, validate it, and append its watches to an issuer.
Parameters
Name
Type
Description
issuer
Handler owner.
watch_specsis created or extended; optionalarti_filesaliases are rewritten to resolved names.file
str
Configuration filename relative to the template directory.
guid
str
Value substituted for
{GUID}before glob normalization.ext_path
str | None
Optional path for the sanitized configuration. A directory receives
extension_rules.json.ext_obj
bool
Include the sanitized configuration in the return value.
Returns
Type
Description
bool | dict | tuple
Truenormally, the sanitized dictionary when onlyext_objis enabled, or(True, sanitized)when both output options are enabled.Raises
Exception
Description
ValueError
Reading, validation, handler resolution, alias mapping, or sanitized-output writing fails.
Examples
Load rules for one execution GUID:
public_rules = template.load( issuer, "scanner.json", guid="run-123", ext_obj=True, )
- class ddp_utils.file_scanner.FileEvent(type: str, watch: str, file_kind: str, filename: str, file_path: str, fired_at: float, mtime: float, size: int, stable_checks: int, stable_delay: float, remove_after_handled: bool, decision_on_success: ScanDecision, scan_started_at: float, scan_uptime_s: float)¶
Bases:
objectDescribe one stable file delivered to a handler.
Variables
Name
Type
Description
type
str
Event type, currently
"file".watch
str
Exact name or glob pattern that matched.
file_kind
str
Diagnostic category from the watch.
filename
str
Basename of the matched file.
file_path
str
Absolute matched path.
fired_at
float
Detection timestamp after stabilization.
mtime
float
File modification timestamp.
size
int
File size in bytes.
stable_checks
int
Required unchanged observations.
stable_delay
float
Delay between stability observations.
remove_after_handled
bool
Current deletion policy.
decision_on_success
Current fallback decision.
scan_started_at
float
Scanner start timestamp.
scan_uptime_s
float
Scanner age when the event fired.
Examples
Read the stable path in a handler:
def handle(event): print(event.file_path, event.size)
- class ddp_utils.file_scanner.HandlerReply(ok: bool = True, decision: ScanDecision | None = None, remove_after_handled: bool | None = None, message: str = '', extra: Dict[str, ~typing.Any]=<factory>)¶
Bases:
objectReturn structured success and per-event policy overrides from a handler.
Variables
Name
Type
Description
ok
bool
Treat the event as successful when
True; otherwise apply the scanner’s handler-error policy.decision
ScanDecision | None
Override the watch decision, or
Noneto use its default.remove_after_handled
bool | None
Override deletion, or
Noneto use the watch.message
str
Optional diagnostic text.
extra
Dict[str, Any]
Additional diagnostic or metric fields.
Examples
Keep a parsed file and continue polling:
return HandlerReply( decision=ScanDecision.CONTINUE, remove_after_handled=False, )
- class ddp_utils.file_scanner.FatalEvent(reason: StopReason, message: str, when_ts: float, base_dir: str, filename: str | None = None, file_path: str | None = None, file_kind: str | None = None, exception_type: str | None = None, exception_message: str | None = None, exception_traceback: str | None = None, extra: Dict[str, ~typing.Any]=<factory>)¶
Bases:
objectCarry complete context to the scanner’s fatal-event callback.
Variables
Name
Type
Description
reason
Structured terminal reason.
message
str
Human-readable explanation.
when_ts
float
Event timestamp.
base_dir
str
Watched directory.
filename
str | None
Related basename, when applicable.
file_path
str | None
Related full path, when applicable.
file_kind
str | None
Related watch category, when applicable.
exception_type
str | None
Exception class name, when applicable.
exception_message
str | None
Exception text, when applicable.
exception_traceback
str | None
Formatted traceback, when applicable.
extra
Dict[str, Any]
Additional scanner context.
Examples
Persist fatal diagnostics from a callback:
def on_fatal(event): print(event.reason, event.exception_message)
- class ddp_utils.file_scanner.AsyncScan(scanner: FileScanner, kwargs: dict, daemon: bool = False, wait_timeout: float | None = None)¶
Bases:
objectControl one
FileScannerrunning on a background thread.Examples
Start and wait through the scanner’s public factory:
task = scanner.run_async(wait_timeout=30) result = task.wait()
Configure a background scan without starting it.
Parameters
Name
Type
Description
scanner
Scanner whose
FileScanner.run()method executes.kwargs
dict
Keyword arguments forwarded to
scanner.run.daemon
bool
Create a daemon thread when
True.wait_timeout
Optional[float]
Default timeout used by
wait().Examples
Prefer
FileScanner.run_async()for construction and start:task = scanner.run_async(wait_timeout=30)
- start() None¶
Start the configured scan on a new background thread.
Raises
Exception
Description
RuntimeError
This controller has already been started.
Examples
Start a manually constructed controller once:
task.start()
- wait(timeout: float | None = None) ScanResult¶
Wait for the background scanner and return its terminal result.
Parameters
Name
Type
Description
timeout
float | None
Maximum seconds to wait.
Noneuses the configured default; when both areNone, waiting is unbounded.Returns
Type
Description
Result produced by
FileScanner.run().Raises
Exception
Description
TimeoutError
The scan does not finish within the effective limit.
Examples
Wait at most ten seconds:
result = task.wait(timeout=10)
- stop() None¶
Request cooperative termination of the underlying scanner.
Examples
Cancel a background scan:
task.stop()
- is_alive() bool¶
Return whether the background scan thread is running.
Returns
Type
Description
bool
Trueonly after start and before thread termination.Examples
Avoid starting conflicting project work:
if task.is_alive(): print("scan in progress")
- class ddp_utils.file_scanner.FileScanner(base_dir: str, *, poll_min: float = 0.25, poll_max: float = 1.0, stable_checks: int = 2, stable_delay: float = 0.15, delete_delay: float = 0.8, on_error: Callable[[str, Exception], None] | None = None, on_info: Callable[[str], None] | None = None, on_fatal: Callable[[FatalEvent], None] | None = None, default_max_retries: int = 0, default_retry_delay: float = 2.0, show_info: bool = False, debug: bool = False)¶
Bases:
objectPoll a directory and dispatch stable matching files to handlers.
run()converts timeouts, interrupts, health failures, and configured fatal errors intoScanResultvalues instead of propagating them. Callers may mapUSER_INTERRUPTto process exit code 130 when desired.Examples
Create a low-latency scanner:
scanner = FileScanner("downloads", poll_min=0.1, poll_max=1.0)
Configure polling, stability, callbacks, and retry defaults.
Parameters
Name
Type
Description
base_dir
str
Directory containing signal files.
poll_min
float
Minimum delay between polling cycles.
poll_max
float
Maximum adaptive polling delay.
stable_checks
int
Consecutive unchanged size/mtime observations needed before dispatch.
stable_delay
float
Delay between stability observations.
delete_delay
float
Delay before deleting a handled file.
on_error
Optional[Callable[[str, Exception], None]]
Callback receiving context text and a recoverable error.
on_info
Optional[Callable[[str], None]]
Callback receiving informational messages.
on_fatal
Optional[Callable[[FatalEvent], None]]
Callback receiving
FatalEvent.default_max_retries
int
Default retry limit; zero means unlimited.
default_retry_delay
float
Default seconds between retries.
show_info
bool
Enable informational callback delivery.
debug
bool
Enable detailed diagnostic callback delivery.
Examples
Capture recoverable failures:
scanner = FileScanner( "downloads", on_error=lambda context, error: print(context, error), )
- add_watch(spec: WatchSpec) None¶
Add a watch or replace the watch with the same filename key.
Parameters
Name
Type
Description
spec
Watch specification keyed by
spec.filename.Examples
Register a completion signal:
scanner.add_watch(WatchSpec("done.json", handle_done))
- update_watches(specs: Dict[str, WatchSpec]) None¶
Replace every active watch with a supplied mapping.
Parameters
Name
Type
Description
specs
Dict[str, WatchSpec]
Mapping from watch keys to specifications. A shallow copy is stored and existing error counters for matching keys persist.
Examples
Install a prepared watch set:
scanner.update_watches({spec.filename: spec})
- stop() None¶
Request cooperative termination of the polling loop.
Examples
Stop a scanner from another thread:
scanner.stop()
- run(*, timeout_seconds: int | None = None, idle_timeout_seconds: int | None = None, init_deadline_seconds: int | None = None, init_file: str | None = None, on_init_timeout: Callable[[], None] | None = None, on_timeout: Callable[[], None] | None = None, on_idle_timeout: Callable[[], None] | None = None, stage_idle_timeouts: Mapping[str, int] | None = None, default_max_retries: int | None = None, default_retry_delay: float | None = None, health_check: Callable[[], bool] | None = None, health_check_interval: float = 5.0) ScanResult¶
Run the blocking polling loop until a terminal condition occurs.
Parameters
Name
Type
Description
timeout_seconds
int | None
Absolute lifetime limit, regardless of activity.
idle_timeout_seconds
int | None
Maximum time without any matching file. Detection resets this timer before stability is confirmed.
init_deadline_seconds
int | None
Time allowed for
init_fileto appear.init_file
str | None
Relative startup-signal filename.
on_init_timeout
Callable[[], None] | None
Callback for initialization timeout.
on_timeout
Callable[[], None] | None
Callback for global timeout.
on_idle_timeout
Callable[[], None] | None
Callback for idle timeout.
stage_idle_timeouts
Mapping[str, int] | None
Fallback mapping of file kind to idle timeout.
default_max_retries
int | None
Per-run retry-limit override; zero is unlimited.
default_retry_delay
float | None
Per-run retry-delay override in seconds.
health_check
Callable[[], bool] | None
Callable returning whether an external dependency is healthy.
FalseproducesHEALTH_CHECK_FAILED.health_check_interval
float
Seconds between health checks.
Returns
Type
Description
Structured terminal result. The loop handles
KeyboardInterruptand runtime failures internally.Examples
Wait up to one minute for a completion signal:
result = scanner.run(timeout_seconds=60)
- run_async(daemon: bool = False, poll_max: float | None = None, wait_timeout: float | None = None, **kwargs) AsyncScan¶
Start one background scan and return its controller.
Parameters
Name
Type
Description
daemon
bool
Run on a daemon thread.
poll_max
float | None
Positive temporary maximum poll delay. The original value is restored after completion.
wait_timeout
float | None
Default timeout for
AsyncScan.wait().**kwargs
Arguments forwarded to
run().Returns
Type
Description
Started asynchronous scan controller.
Raises
Exception
Description
RuntimeError
Another background scan is still running.
Examples
Start a background scan with a bounded wait:
task = scanner.run_async( wait_timeout=30, timeout_seconds=120, )
- static example()¶
Print a complete configuration-and-scanning usage example.
Examples
Display the sample in an interactive session:
FileScanner.example()