execlib/execlog/routers/path.py

91 lines
3.9 KiB
Python

import logging
from pathlib import Path
from execlog.router import Router
from execlog.listeners.path import PathListener
from execlog.util.path import glob_match
logger = logging.getLogger(__name__)
class PathRouter(Router):
def __init__(self, loop=None, workers=None, listener_cls=PathListener):
'''
Parameters:
workers: number of workers to assign the thread pool when the event loop is
started. Defaults to `None`, which, when passed to
ThreadPoolExecutor, will by default use 5x the number of available
processors on the machine (which the docs claim is a reasonable
assumption given threads are more commonly leveraged for I/O work
rather than intense CPU operations). Given the intended context for
this class, this assumption aligns appropriately.
'''
super().__init__(loop=loop, workers=workers, listener_cls=listener_cls)
def register(
self,
path,
func,
glob='**/!(.*|*.tmp|*~)', # recursive, non-temp
debounce=200,
delay=30,
**listener_kwargs,
):
'''
Parameters:
path: Path (directory) to watch with `inotify`
func: Callback to run if FS event target matches glob
glob: Relative glob pattern to match files in provided path. The FS event's
filename must match this pattern for the callback to queued. (Default:
"*"; matching all files in path).
listener_kwargs: Additional params for associated listener "listen" routes.
See `PathListener.listen`.
'''
super().register(
#endpoint=Path(path),
endpoint=path,
callback=func,
pattern=glob,
debounce=debounce,
delay=delay,
**listener_kwargs
)
def filter(self, event, glob, **listen_kwargs) -> bool:
'''
Note:
If `handle_events` is called externally, note that this loop will block in the
calling thread until the jobs have been submitted. It will _not_ block until
jobs have completed, however, as a list of futures is returned. The calling
Watcher instance may have already been started, in which case `run()` will
already be executing in a separate thread. Calling this method externally will
not interfere with this loop insofar as it adds jobs to the same thread pool.
Because this method only submits jobs associated with the provided `events`,
the calling thread can await the returned list of futures and be confident
that top-level callbacks associated with these file events have completed. Do
note that, if the Watcher has already been started, any propagating file
events will be picked up and possibly process simultaneously (although their
associated callbacks will have nothing to do with the return list of futures).
Parameters:
event : Event instance
glob : Single string or tuple of glob patterns to check against event endpoint
'''
not_tmp_glob = '**/!(.*|*.tmp|*~)'
if not glob_match(Path(event.name), not_tmp_glob):
return False
listen_flags = listen_kwargs.get('flags')
# only filter by flags if explicitly specified on registry
# (o/w route likely just wanting to use defaults)
if listen_flags is not None:
# negative filter if action not one of the listened flags
if not any(flag & listen_flags for flag in event.action):
logger.debug(
f'Event [{event.name}] caught in flag filter under [{glob}] for action [{event.action}]'
)
return False
return glob_match(Path(event.name), glob)