91 lines
3.9 KiB
Python
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)
|