diff --git a/.gitignore b/.gitignore index f5efb7f..7f353a1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ *__pycache__ .venv/ *.swp +*egg-info +build/ +dist/ diff --git a/PyNotify.egg-info/PKG-INFO b/PyNotify.egg-info/PKG-INFO deleted file mode 100644 index e16fcb9..0000000 --- a/PyNotify.egg-info/PKG-INFO +++ /dev/null @@ -1,242 +0,0 @@ -Metadata-Version: 2.1 -Name: PyNotify -Version: 0.1 -Summary: An asyncio interface to the Linux inotify API -Home-page: https://github.com/mcriley821/PyNotify -Author: Matt Riley -License: LICENSE.txt -Project-URL: Issues, https://github.com/mcriley821/PyNotify/issues -Keywords: inotify,async,asyncio,asynchronous,monitor,file monitor -Platform: UNKNOWN -Classifier: Programming Language :: Python :: 3 -Classifier: Operating System :: POSIX :: Linux -Classifier: Framework :: AsyncIO -Classifier: Development Status :: 3 - Alpha -Classifier: Intended Audience :: Developers -Classifier: Intended Audience :: System Administrators -Classifier: License :: OSI Approved :: The Unlicense (Unlicense) -Classifier: Natural Language :: English -Classifier: Topic :: System :: Filesystems -Classifier: Topic :: System :: Monitoring -Requires-Python: >=3.10 -Description-Content-Type: text/x-rst - -PyNotify -======== -PyNotify is an async Python interface to the Linux inotify API. - -See `man inotify `_ -for more information regarding inotify details. - -See the `documentation `_! - -Install -------- -To install manually, clone the repo and pip install: - -.. code:: bash - - git clone https://github.com/mcriley821/PyNotify.git - cd PyNotify && pip install . - - -Description ------------ -PyNotify uses the `ctypes `_ -module to interface with the inotify API to allow the user to create -'*watches*' for monitoring filesystem events. These events are parsed -into Event objects, which are then handled by EventHandler objects. - -Any number of EventHandlers can be added to a Notifier instance to handle a -specific watch. This is done when requesting a watch via ``Notifier.add_watch`` -When an Event is emitted for the corresponding watch, each EventHandler is -queried for capability of handling said Event. The Event is subsequently -passed to the EventHandler if it is capable. - -Usage ------ - -Simple use case ---------------- - -As an example, an EventHandler that handles all event types for a watch -could be defined as so: - -.. role:: python(code) - :language: python - -.. code:: python - - class AllHandler: - def handle_event(self, event: Event) -> None: - # just print out what is happening - print(f"File {event.name} {event.type.name} at {event.path}") - - def can_handle_event_type(self, type: EventType) -> bool: - return EventType.ALL & type != 0 - -The :python:`AllHandler` can now be added to a watch via -:python:`Notifier.add_watch`: - -.. code:: python - - async def main(): - with pynotify.Notifier() as notifier: - notifier.add_watch(pathlib.Path.cwd(), AllHandler()) - await notifier.run() - -A slightly more interesting example ------------------------------------ - -.. code:: python - - class OpenHandler: - def handle_event(self, event: Event) -> None: - ... - - def can_handle_event_type(self, type: EventType) -> bool: - return EventType.OPEN & type != 0 - - class CloseHandler: - def handle_event(self, event: Event) -> None: - ... - - def can_handle_event_type(self, type: EventType) -> bool: - return EventType.CLOSE & type != 0 - - async def stop_loop(stop_event: asyncio.Event): - await asyncio.sleep(10) - stop_event.set() - - async def main(): - with pynotify.Notifier() as notifier: - path = pathlib.Path.cwd() - stop_event = asyncio.Event() - - notifier.add_watch(path, OpenHandler(), CloseHandler(), - only_event_types=EventType.OPEN | EventType.CLOSE) - await asyncio.gather( - notifier.run(stop_event=stop_event), - stop_loop(stop_event)) - -The above example will run the Notifier run-loop for 10 seconds, generating -only open and close Events for the watch on the current working directory. - -Adding/Modifying/Removing watches ---------------------------------- - -Watches can be added as simply as we've seen above. There are a few more -options that can be specified when adding a watch: - -.. code:: python - - async def main(): - with pynotify.Notifier() as notifier: - path = pathlib.Path.cwd() - notifier.add_watch( - path, # path to add a watch on - - # any number of handlers for the watch - AllHandler(), OpenHandler(), CloseHandler(), - - # restrict EventTypes generated by the watch - only_event_types=EventTypes.OPEN, - - # raises if False and path is a symlink - follow_symlinks=False, - - # raises if True and path is not a directory - if_directory_only=True, - - # if True, generate a single event then remove the watch - oneshot=False, - - # See the docs for more info on this flag - exclude_unlinks=True) - - -EventTypes for a watch can be modified after it has been added to a Notifier: - -.. code:: python - - async def main(): - with pynotify.Notifier() as notifier: - path = pathlib.Path.cwd() - notifier.add_watch(path) # generates all EventTypes by default - ... - # generate only CLOSE Events - notifier.modify_watch_event_type(path, EventType.CLOSE) - - # merge EventTypes to generate both CLOSE and OPEN Events - notifier.modify_watch_event_type(path, EventType.OPEN, merge=True) - -Watches are easily removed: - -.. code:: python - - async def main(): - with pynotify.Notifier() as notifier: - path = pathlib.Path.cwd() - notifier.add_watch(path) - ... - notifier.remove_watch(path) - # notifier.remove_watch(path) # raises, since path not being watched - notifier.remove_watch(path, raises=False) # don't raise - - -Adding/Removing/Clearing EventHandlers --------------------------------------- -EventHandlers can be added when adding a watch, and can be added or removed -after a watch has already been established: - -.. code:: python - - async def main(): - with pynotify.Notifier() as notifier: - path = pathlib.Path.cwd() - open_handler = OpenHandler() - notifier.add_watch(path, open_handler) # add open_handler to watch - - all_handler = AllHandler() - # add all_handler and a CloseHandler - notifier.add_handlers(path, all_handler, CloseHandler()) - - # remove only the all_handler - notifier.remove_handlers(path, all_handler) - - # clear all handlers on the watch - notifier.clear_handlers(path) - -Note in the above example that the :python:`Notifier.add_watches` and -:python:`Notifier.remove_handlers` method can take any number of EventHandlers -to add or remove. Also, duplicate handlers for a watch are not possible, and -removing a handler that isn't on a watch will do nothing: - -.. code:: python - - async def main(): - with pynotify.Notifier() as notifier: - path = pathlib.Path.cwd() - open_handler = OpenHandler() - - notifier.add_watch(path, open_handler) - - # does nothing, since open_handler already on the watch! - notifier.add_handlers(path, open_handlers) - - notifier.remove_handlers(path, open_handler) # no more handlers - - # does nothing, since open_handler isn't on the watch - notifier.remove_handlers(path, open_handler) - -FAQ ---- -To be filled as questions arise... - - -License -------- -The UNLICENSE. See https://www.unlicense.org for more info. - - - diff --git a/PyNotify.egg-info/SOURCES.txt b/PyNotify.egg-info/SOURCES.txt deleted file mode 100644 index ef6b67c..0000000 --- a/PyNotify.egg-info/SOURCES.txt +++ /dev/null @@ -1,12 +0,0 @@ -README.rst -pyproject.toml -setup.cfg -PyNotify.egg-info/PKG-INFO -PyNotify.egg-info/SOURCES.txt -PyNotify.egg-info/dependency_links.txt -PyNotify.egg-info/top_level.txt -pynotify/__init__.py -pynotify/event.py -pynotify/event_handler.py -pynotify/event_type.py -pynotify/notifier.py \ No newline at end of file diff --git a/PyNotify.egg-info/dependency_links.txt b/PyNotify.egg-info/dependency_links.txt deleted file mode 100644 index 8b13789..0000000 --- a/PyNotify.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/PyNotify.egg-info/top_level.txt b/PyNotify.egg-info/top_level.txt deleted file mode 100644 index f63c452..0000000 --- a/PyNotify.egg-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -pynotify diff --git a/README.rst b/README.rst index fb3827b..050dec3 100644 --- a/README.rst +++ b/README.rst @@ -47,7 +47,7 @@ could be defined as so: class AllHandler: def handle_event(self, event: Event) -> None: # just print out what is happening - print(f"File {event.name} {event.type.name} at {event.path}") + print(f"{event.type.name} at {event.file_path}") def can_handle_event_type(self, type: EventType) -> bool: return EventType.ALL & type != 0 diff --git a/build/lib/pynotify/__init__.py b/build/lib/pynotify/__init__.py deleted file mode 100644 index 3b0e3ea..0000000 --- a/build/lib/pynotify/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env python - -#: An inotify watch descriptor. A simple type alias for :py:class:`int` -WatchDescriptor = int - -from .event_type import EventType -from .event import Event -from .event_handler import EventHandler -from .notifier import Notifier - -__all__ = ["Event", "EventType", "EventHandler", "Notifier", "WatchDescriptor"] diff --git a/build/lib/pynotify/event.py b/build/lib/pynotify/event.py deleted file mode 100644 index 206088e..0000000 --- a/build/lib/pynotify/event.py +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env python -from __future__ import annotations -from dataclasses import dataclass -from typing import Callable -from pathlib import Path -import struct - -from . import EventType, WatchDescriptor - -ISDIR = 0x4000_0000 -UNMOUNTED = 0x0000_2000 - -@dataclass(frozen=True) -class Event: - """Events_ port an inotify_event struct into a Python class, - with some additional attributes. - - :note: An Event_ is a frozen :py:func:`~dataclasses.dataclass` - :raises dataclasses.FrozenInstanceError: - Upon attempted attribute change - """ - - #: Corresponding inotify watch descriptor of this Event_ - watch_descriptor: WatchDescriptor - - type: EventType #: EventType_ of this Event_ - is_directory: bool #: :data:`True` if the watched file is a directory - unmounted: bool #: :data:`True` if the watched file was unmounted - cookie: int #: Corresponding inotify cookie - file_name: str #: File name that caused this Event_ - - #: :class:`~pathlib.Path` to the file that caused this Event_ - file_path: Path - - @staticmethod - def from_buffer( - wd_to_path: Callable[ [WatchDescriptor], Path], - buffer: bytes, - offset: int = 0) -> tuple[Event, int]: - """Starting at *offset*, unpack *buffer* to create - an Event_ object. - - :param wd_to_path: A callable to convert a :data:`WatchDescriptor` - into a :class:`~pathlib.Path` - :param buffer: The buffer to unpack from - :param offset: An offset into *buffer* to begin unpacking - - :return: :class:`tuple` of the new Event_ and new *offset* - """ - # Unpack raw inotify_event struct - wd, mask, cookie, _len = struct.unpack_from("@iIII", buffer, offset) - offset += struct.calcsize("@iIII") - file_name, = struct.unpack_from(f"@{_len}s", buffer, offset) - offset += struct.calcsize(f"@{_len}s") - - # Process into Event attributes - file_name = file_name.strip(b'\0').decode() - file_path = wd_to_path(wd) / file_name - type = EventType(mask & EventType.ALL) - - is_dir = (mask & ISDIR) != 0 - unmounted = (mask & UNMOUNTED) != 0 - - # Create and return - event = Event(watch_descriptor=wd, type=type, - is_directory=is_dir, unmounted=unmounted, - cookie=cookie, file_name=file_name, file_path=file_path) - - return event, offset - diff --git a/build/lib/pynotify/event_handler.py b/build/lib/pynotify/event_handler.py deleted file mode 100644 index fa6b3d6..0000000 --- a/build/lib/pynotify/event_handler.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env python -from typing import Protocol - -from . import Event, EventType - - -class EventHandler(Protocol): - """Outlines an EventHandler_ object. - - Conforming objects can be added to a Notifier_ instance via - :func:`Notifier.add_watch`. - """ - - def handle_event(self, event: Event) -> None: - """Handle the provided Event_ *event* - - :param event: An Event_ generated by the corresponding watch - added with :func:`Notifier.add_watch` - """ - - def can_handle_event_type(self, type: EventType) -> bool: - """A method required by a Notifier_ to query this EventHandler_ - on whether it can handle the provided EventType_ *type* - - :param type: EventType_ to be handled - - :return bool: :data:`True` if this EventHandler_ can handle the - provided EventType_ *type* - """ diff --git a/build/lib/pynotify/event_type.py b/build/lib/pynotify/event_type.py deleted file mode 100644 index 5276071..0000000 --- a/build/lib/pynotify/event_type.py +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env python -from enum import IntFlag - - -class EventType(IntFlag): - """EventTypes_ map to different filesystem events. - - In the man_ page, event types are listings under the - *inotify events* section. (They also correspond in name.) - """ - - ACCESS = 0x001 #: File was accessed - MODIFY = 0x002 #: File was modified - ATTRIB = 0x004 #: File attribute metadata changed - CLOSE_WRITE = 0x008 #: File opened for writing was closed - CLOSE_NOWRITE = 0x010 #: File opened for reading only was closed - OPEN = 0x020 #: File was opened - MOVED_FROM = 0x040 #: A file was moved from the watch directory - MOVED_TO = 0x080 #: A file was moved to the watch directory - CREATE = 0x100 #: File was created - DELETE = 0x200 #: File was deleted - DELETE_SELF = 0x400 #: File was deleted - MOVE_SELF = 0x800 #: File was moved - - #: File was closed: (:py:attr:`CLOSE_WRITE` | :py:attr:`CLOSE_NOWRITE`) - CLOSE = CLOSE_WRITE | CLOSE_NOWRITE - - #: File was moved: (:py:attr:`MOVED_FROM` | :py:attr:`MOVED_TO`) - MOVED = MOVED_FROM | MOVED_TO - - #: Convenience EventType_ for all above EventTypes_ - ALL = 0xfff # combined value for brevity - diff --git a/build/lib/pynotify/notifier.py b/build/lib/pynotify/notifier.py deleted file mode 100644 index 59a5ff5..0000000 --- a/build/lib/pynotify/notifier.py +++ /dev/null @@ -1,344 +0,0 @@ -#!/usr/bin/env python -from __future__ import annotations -import asyncio -import ctypes -import errno -import os - -from ctypes import c_int, c_uint, c_char_p -from ctypes.util import find_library - -from fcntl import ioctl -from termios import FIONREAD -from signal import SIGINT - -from pathlib import Path - -from typing import AsyncIterable - -from . import Event, EventType, EventHandler, WatchDescriptor - - -ONLYDIR = 24 # shift for 0x0100_0000 -DONT_FOLLOW = 25 # shift for 0x0200_0000 -EXCL_UNLINK = 26 # shift for 0x0400_0000 -ONESHOT = 31 # shift for 0x8000_0000 - -MASK_ADD = 29 # shift for 0x2000_0000 - - -class TwoWayDict(dict): - """Dict subclass to automatically add a value: key pair - whenever a key: value pair is added. - - This is for convenience sake. Instead of tracking two - dicts in the owning object, using this class requires - only one. - """ - def __setitem__(self, key, value): - """Add the key: value pair and the value: key pair""" - dict.__setitem__(self, key, value) - dict.__setitem__(self, value, key) - - -def load_libc(path: Path | None = None) -> ctypes.CDLL: - """Load libc and return a ctypes.CDLL. - - :param path: An optional path to libc.so - - :raises FileNotFoundError: When libc can't be found - """ - if path is None: - path = find_library("c") - if path is None: - raise FileNotFoundError("Could not find libc!") - return ctypes.CDLL(path) - - -class Notifier: - """Notifier - """ - def __init__(self, - async_loop: asyncio.AbstractEventLoop | None = None, - libc_path: Path | None = None): - self._libc = load_libc(libc_path) - self._define_inotify_functions() - - self._loop = async_loop if async_loop else asyncio.get_running_loop() - self._wd_paths: TwoWayDict[WatchDescriptor, Path] = TwoWayDict() - self._queue = asyncio.Queue() - self._handlers: dict[WatchDescriptor, set[EventHandler]] = {} - - self._fd = self._inotify_init() - self._loop.add_reader(self._fd, self._on_fd_ready_to_read) - - def _define_inotify_functions(self): - """Define inotify functions from libc as required by ctypes. - Note that return values are error-checked by the - _handle_inotify_return method.""" - self._libc.inotify_init.restype = self._handle_inotify_return - self._libc.inotify_init.argtypes = tuple() - - self._libc.inotify_add_watch.restype = self._handle_inotify_return - self._libc.inotify_add_watch.argtypes = (c_int, c_char_p, c_uint) - - self._libc.inotify_rm_watch.restype = self._handle_inotify_return - self._libc.inotify_rm_watch.argtypes = (c_int, c_int) - - @staticmethod - def _handle_inotify_return(value: int) -> int: - """Handle the return from an inotify function. Each inotify function - will return -1 to indicate an error and set errno accordingly. - Otherwise the return is valid.""" - if value == -1: - err = ctypes.get_errno() - raise RuntimeError(f"{errno.errorcode[err]}: {os.strerror(err)}") - return value - - def _inotify_init(self) -> int: - """Initializes a new inotify instance and returns a - file descriptor associated with the new inotify event queue.""" - return self._libc.inotify_init() - - def _on_fd_ready_to_read(self): - """Callback method when the asyncio loop determines that the - inotify file descriptor is ready to read.""" - raw_data = self._read_from_fd() - self._create_and_put_events(raw_data) - - def _read_from_fd(self) -> bytes: - """Read and return all bytes available on the file descriptor. - - :return: raw :class:`bytes` from the file descriptor - """ - available = c_int() - try: - ioctl(self._fd, FIONREAD, available) - except OSError: # happens if last watch is removed - return b"" - return os.read(self._fd, available.value) - - def _create_and_put_events(self, raw_data: bytes): - """Deserialize Event objects from the raw data and put - the Event on the queue. - - :param raw_data: The raw bytes to convert to Events_ - """ - offset = 0 - while offset != len(raw_data): - event, offset = Event.from_buffer( - self.watch_descriptor_to_path, raw_data, offset) - self._queue.put_nowait(event) - - def watch_descriptor_to_path(self, - watch_descriptor: WatchDescriptor) -> Path: - """Return the corresponding :class:`Path` of a WatchDescriptor_ - - :param watch_descriptor: The WatchDescriptor_ to convert to a - :class:`Path` - """ - return self._wd_paths[watch_descriptor] - - def add_watch(self, - file_path: Path, - *handlers: EventHandler, - only_event_types: EventType = EventType.ALL, - follow_symlinks: bool = True, - if_directory_only: bool = False, - oneshot: bool = False, - exclude_unlinks: bool = True): - """Add a watch to the specified *file_path*. - - :param file_path: :class:`Path` to watch - :param handlers: Any number of EventHandlers_ to handle - Events_ from the created watch - - :param only_event_types: Generate an Event_ only for the - specified EventTypes_ - - :param follow_symlinks: If :data:`True`, watch even if *file_path* - is a symlink - - :param if_directory_only: If :data:`True`, watch only if *file_path* - is a directory - - :param oneshot: If :data:`True`, delete the watch on *file_path* - after the first Event_ is generated - - :param exclude_unlinks: Stop watching children when unlinked from - a watch directory. See the man_ for - more details. - - :raises FileNotFoundError: If *file_path* does not exist - :raises ValueError: If a watch already exists for *file_path* - :raises OSError: If *follow_symlinks* is :data:`False` and - *file_path* is a symlink - :raises OSError: If *if_directory_only* is :data:`True` and - *file_path* is not a directory - """ - file_path = file_path.expanduser() - if not file_path.exists(): - raise FileNotFoundError(f"Cannot find file: {file_path}") - if file_path in self._wd_paths: - raise ValueError(f"File '{file_path}' already has a watch!") - - bytes_path = str(file_path).encode() - - mask = ( only_event_types - | (follow_symlinks << DONT_FOLLOW) - | (if_directory_only << ONLYDIR) - | (exclude_unlinks << EXCL_UNLINK) - | (oneshot << ONESHOT) ) - - wd = self._libc.inotify_add_watch(self._fd, bytes_path, mask) - self._wd_paths[wd] = file_path - self._handlers[wd] = set(handlers) - - def modify_watch_event_types(self, - descriptor: Path | WatchDescriptor, - new_types: EventType, - merge: bool = False): - """Modify the possible Events_ that can be generated for - the watch at *descriptor* by modifying the EventTypes_ for - said watch. - - :param descriptor: A :class:`Path` or WatchDescriptor_ to change - the EventTypes_ for - - :param new_types: EventTypes_ of Events_ to generate - :param merge: If :data:`True` merge *new_types* with the existing - EventTypes_ for the watch - - :raises ValueError: If there is no watch on *descriptor* - - """ - if isinstance(descriptor, Path): - descriptor = descriptor.expanduser() - - if not descriptor in self._wd_paths: - raise ValueError(f"No watch on descriptor '{descriptor}'!") - - if isinstance(descriptor, Path): - file_path = descriptor - else: - file_path = self._wd_paths[descriptor] - bytes_path = str(file_path).encode() - - # inotify_add_watch also modifies existing watch masks - mask = new_types | (merge << MASK_ADD) - _ = self._libc.inotify_add_watch(self._fd, bytes_path, mask) - - def remove_watch(self, - descriptor: Path | WatchDescriptor, - raises: bool = True): - """Remove a watch on the *descriptor* - - :param descriptor: A :class:`Path` or WatchDescriptor_ to remove - :param raises: If :data:`True`, raise a :class:`ValueError` if - the *descriptor* does not have a watch - - :raises ValueError: If *raises* and there is no watch on *descriptor* - """ - if isinstance(descriptor, Path): - descriptor = descriptor.expanduser() - if descriptor not in self._wd_paths: - if raises: - raise ValueError(f"No watch on descriptor '{descriptor}'!") - return - - wd = self._wd_paths.pop(descriptor) - del self._wd_paths[wd] - - if isinstance(wd, Path): - wd = descriptor - - self._libc.inotify_rm_watch(self._fd, wd) - - def add_handlers(self, - descriptor: Path | WatchDescriptor, - *handlers: EventHandler): - """Add the EventHandlers_ *handlers* to the watch on *descriptor* - - .. note:: Skips a handler if it is already on the *descriptor* - - :param descriptor: A :class:`Path` or WatchDescriptor_ to add - *handlers* to - :param handlers: New EventHandlers_ to add to *descriptor* - """ - if isinstance(descriptor, Path): - descriptor = self._wd_paths[descriptor] - self._handlers[descriptor] |= set(handlers) - - def clear_handlers(self, descriptor: Path | WatchDescriptor): - """Clear all EventHandlers_ for the watch on *descriptor* - - :param descriptor: A :class:`Path` or WatchDescriptor_ to - clear EventHandlers_ from - - :raises ValueError: If there is no watch on *descriptor* - """ - if descriptor not in self._wd_paths: - raise ValueError(f"No watch on descriptor '{descriptor}'!") - if isinstance(descriptor, Path): - descriptor = self._wd_paths[descriptor] - self._handlers[descriptor].clear() - - def remove_handlers(self, - descriptor: Path | WatchDescriptor, - *handlers: EventHandler): - """Remove the EventHandlers_ *handlers* from the watch on *descriptor* - - :param descriptor: A :class:`Path` or WatchDescriptor_ to remove - EventHandlers_ from - :param handlers: EventHandlers_ to remove from *descriptor* - - :raises ValueError: If there is no watch on *descriptor* - """ - if descriptor not in self._wd_paths: - raise ValueError("No watch on descriptor '{descriptor}'!") - if isinstance(descriptor, Path): - descriptor = self._wd_paths[descriptor] - self._handlers[descriptor] -= set(handlers) - - async def run(self, - stop_event: asyncio.Event | None = None, - handle_once: bool = False, - warn_unhandled: bool = True): - """Asynchronously generate Events_ until *stop_event* is set. - When an Event_ is generated, the :func:`EventHandler.handle_event` - method for each EventHandler_ for the watch, from - :func:`Notifier.add_watch`, is called with the generated Event_. - - :param stop_event: :class:`asyncio.Event` to stop generating - Events_ when set. - :param handle_once: If :data:`True` the generated Event_ is - discarded after it is handled by the first - capable EventHandler_. - :param warn_unhandled: Emit a warning via :func:`warnings.warn` - if an Event_ is not handled. - """ - while stop_event is None or not stop_event.is_set(): - event = await self._queue.get() - handled = False - for handler in self._handlers[event.watch_descriptor]: - if not handler.can_handle_event_type(event.type): - continue - handler.handle_event(event) - handled = True - if handle_once: - break - if warn_unhandled and not handled: - warnings.warn("Unhandled Event! {event}", RuntimeWarning) - - def close(self): - """Close the inotify fd""" - os.close(self._fd) - - def __enter__(self): - """Nothing special on entry""" - return self - - def __exit__(self, *exception): - """Close the inotify fd when exiting""" - self.close() - diff --git a/dist/PyNotify-0.1-py3-none-any.whl b/dist/PyNotify-0.1-py3-none-any.whl deleted file mode 100644 index 2eccb67..0000000 Binary files a/dist/PyNotify-0.1-py3-none-any.whl and /dev/null differ diff --git a/dist/PyNotify-0.1.tar.gz b/dist/PyNotify-0.1.tar.gz deleted file mode 100644 index e3e6734..0000000 Binary files a/dist/PyNotify-0.1.tar.gz and /dev/null differ