Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#16: Inited the filtering functionality #34

Merged
merged 3 commits into from
Jan 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "notifykit_lib"
version = "0.0.7-alpha.1"
version = "0.0.8-alpha.1"
edition = "2021"
license = "A toolkit for building applications watching filesystem changes"
homepage = "https://github.com/roma-glushko/notifykit"
Expand Down
6 changes: 5 additions & 1 deletion notifykit/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from notifykit._notifier import Notifier, NotifierT, Event
from notifykit._filters import EventFilter, CommonFilter
from notifykit._notifier import Notifier, NotifierT
from notifykit._notifykit_lib import (
__version__,
ObjectType,
Expand All @@ -17,13 +18,16 @@
)

from notifykit._testing import NotifierMock
from notifykit._typing import Event

VERSION = __version__

__all__ = (
"Notifier",
"NotifierT",
"VERSION",
"EventFilter",
"CommonFilter",
"Event",
"ObjectType",
"AccessType",
Expand Down
96 changes: 96 additions & 0 deletions notifykit/_filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import re
from pathlib import Path
from typing import Sequence, Union

from notifykit._notifykit_lib import RenameEvent

from notifykit._typing import Event


class EventFilter:
"""
A base class to define rules to filter filesystem events
TODO: Move filtering to the Rust library
"""

__slots__ = "_ignore_dirs", "_ignore_object_regexes", "_ignore_paths"

ignore_dirs: Sequence[str] = ()
"""Full names of directories to ignore like `.git`."""

ignore_object_patterns: Sequence[str] = ()
"""
Patterns of files or directories to ignore, these are compiled into regexes.
"""

ignore_paths: Sequence[Union[str, Path]] = ()
"""
Full paths to ignore, e.g. `/home/users/.cache` or `C:\\Users\\user\\.cache`.
"""

def __init__(self) -> None:
self._ignore_dirs = set(self.ignore_dirs)
self._ignore_object_regexes = tuple(re.compile(r) for r in self.ignore_object_patterns)
self._ignore_paths = tuple(map(str, self.ignore_paths))

def __call__(self, event: Event) -> bool:
"""
Check if event should be filtered (True) or kept in place (False)
"""
if isinstance(event, RenameEvent):
return self._should_be_filtered(Path(event.old_path)) and self._should_be_filtered(Path(event.new_path))

return self._should_be_filtered(Path(event.path))

def _should_be_filtered(self, path: Path) -> bool:
if any(p in self._ignore_dirs for p in path.parts):
return True

object_name = path.name

if any(r.search(object_name) for r in self._ignore_object_regexes):
return True

if self._ignore_paths:
for ignore_path in self._ignore_paths:
if path.is_relative_to(ignore_path):
return True

return False

def __repr__(self) -> str:
args = ", ".join(f"{k}={getattr(self, k, None)!r}" for k in self.__slots__)

return f"{self.__class__.__name__}({args})"


class CommonFilter(EventFilter):
"""
Filter commonly ignored files and directories
"""

ignore_dirs: Sequence[str] = (
"__pycache__",
".git",
".hg",
".svn",
".tox",
".venv",
"site-packages",
".idea",
"node_modules",
".mypy_cache",
".ruff_cache",
".pytest_cache",
".hypothesis",
)

ignore_object_patterns: Sequence[str] = (
r"\.py[cod]$",
r"\.___jb_...___$",
r"\.sw.$",
"~$",
r"^\.\#",
r"^\.DS_Store$",
r"^flycheck_",
)
33 changes: 24 additions & 9 deletions notifykit/_notifier.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
from os import PathLike
import anyio
import logging
from typing import Sequence, Protocol, Optional, List
from notifykit._notifykit_lib import (
WatcherWrapper,
AccessEvent,
CreateEvent,
ModifyDataEvent,
ModifyMetadataEvent,
ModifyOtherEvent,
DeleteEvent,
RenameEvent,
)

Event = AccessEvent | CreateEvent | ModifyDataEvent | ModifyMetadataEvent | ModifyOtherEvent | DeleteEvent | RenameEvent
from notifykit._typing import Event
from notifykit._filters import EventFilter

logger = logging.getLogger(__name__)


class AnyEvent(Protocol):
Expand Down Expand Up @@ -51,13 +48,19 @@ class Notifier:
"""

def __init__(
self, debounce_ms: int = 200, tick_ms: int = 50, debug: bool = False, stop_event: Optional[AnyEvent] = None
self,
debounce_ms: int = 200,
tick_ms: int = 50,
debug: bool = False,
filter: Optional[EventFilter] = None,
stop_event: Optional[AnyEvent] = None,
) -> None:
self._debounce_ms = debounce_ms
self._tick_ms = tick_ms
self._debug = debug

self._watcher = WatcherWrapper(debounce_ms, debug)
self._filter = filter
self._stop_event = stop_event if stop_event else anyio.Event()

def watch(
Expand Down Expand Up @@ -89,6 +92,12 @@ def __next__(self) -> List[Event]:
if events is None:
raise StopIteration

if self._filter:
if self._debug:
logger.debug(f"events before filtering: {events}")

events = [event for event in events if not self._filter(event)]

return events

async def __anext__(self) -> List[Event]:
Expand All @@ -107,4 +116,10 @@ async def __anext__(self) -> List[Event]:
if events is None:
raise StopAsyncIteration

if self._filter:
if self._debug:
logger.debug(f"events before filtering: {events}")

events = [event for event in events if not self._filter(event)]

return events
2 changes: 1 addition & 1 deletion notifykit/_testing.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from os import PathLike
from typing import Sequence, List, Optional

from notifykit import Event
from notifykit._typing import Event
from notifykit._notifier import AnyEvent


Expand Down
11 changes: 11 additions & 0 deletions notifykit/_typing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from notifykit._notifykit_lib import (
AccessEvent,
CreateEvent,
ModifyDataEvent,
ModifyMetadataEvent,
ModifyOtherEvent,
DeleteEvent,
RenameEvent,
)

Event = AccessEvent | CreateEvent | ModifyDataEvent | ModifyMetadataEvent | ModifyOtherEvent | DeleteEvent | RenameEvent
Loading
Loading