From ecdab7bbec52f4a88407ce9154983423a07b07f6 Mon Sep 17 00:00:00 2001 From: Malthe Borch Date: Thu, 18 Apr 2024 22:18:54 +0200 Subject: [PATCH 1/4] Sort imports --- fsevents.py | 55 +++++++++++++++++++++++++------------------------- pyproject.toml | 2 ++ setup.py | 5 +++-- tests.py | 11 +++++----- 4 files changed, 39 insertions(+), 34 deletions(-) create mode 100644 pyproject.toml diff --git a/fsevents.py b/fsevents.py index 213bf4b..fbd657a 100644 --- a/fsevents.py +++ b/fsevents.py @@ -4,43 +4,44 @@ import unicodedata from _fsevents import ( - loop, - stop, - schedule, - unschedule, CF_POLLIN, CF_POLLOUT, - FS_IGNORESELF, - FS_FILEEVENTS, - FS_ITEMCREATED, - FS_ITEMREMOVED, - FS_ITEMINODEMETAMOD, - FS_ITEMRENAMED, - FS_ITEMMODIFIED, - FS_ITEMFINDERINFOMOD, - FS_ITEMCHANGEOWNER, - FS_ITEMXATTRMOD, - FS_ITEMISFILE, - FS_ITEMISDIR, - FS_ITEMISSYMLINK, + FS_CFLAGFILEEVENTS, + FS_CFLAGIGNORESELF, + FS_CFLAGNODEFER, + FS_CFLAGNONE, + FS_CFLAGUSECFTYPES, + FS_CFLAGWATCHROOT, FS_EVENTIDSINCENOW, + FS_FILEEVENTS, FS_FLAGEVENTIDSWRAPPED, - FS_FLAGNONE, FS_FLAGHISTORYDONE, - FS_FLAGROOTCHANGED, FS_FLAGKERNELDROPPED, - FS_FLAGUNMOUNT, FS_FLAGMOUNT, - FS_FLAGUSERDROPPED, FS_FLAGMUSTSCANSUBDIRS, - FS_CFLAGFILEEVENTS, - FS_CFLAGNONE, - FS_CFLAGIGNORESELF, - FS_CFLAGUSECFTYPES, - FS_CFLAGNODEFER, - FS_CFLAGWATCHROOT, + FS_FLAGNONE, + FS_FLAGROOTCHANGED, + FS_FLAGUNMOUNT, + FS_FLAGUSERDROPPED, + FS_IGNORESELF, + FS_ITEMCHANGEOWNER, + FS_ITEMCREATED, + FS_ITEMFINDERINFOMOD, + FS_ITEMINODEMETAMOD, + FS_ITEMISDIR, + FS_ITEMISFILE, + FS_ITEMISSYMLINK, + FS_ITEMMODIFIED, + FS_ITEMREMOVED, + FS_ITEMRENAMED, + FS_ITEMXATTRMOD, + loop, + schedule, + stop, + unschedule ) + class Mask(int): stringmap = { FS_FLAGMUSTSCANSUBDIRS: 'MustScanSubDirs', diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..e93ae64 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,2 @@ +[tool.isort] +multi_line_output=3 diff --git a/setup.py b/setup.py index d7f9bb0..e6b78c5 100644 --- a/setup.py +++ b/setup.py @@ -1,8 +1,9 @@ import os -from setuptools.extension import Extension -from setuptools.command.build_ext import build_ext from setuptools import setup +from setuptools.command.build_ext import build_ext +from setuptools.extension import Extension + def read(fname): with open(os.path.join(os.path.dirname(__file__), fname)) as f: diff --git a/tests.py b/tests.py index 28109e7..178e0f5 100644 --- a/tests.py +++ b/tests.py @@ -1,5 +1,6 @@ import unittest + class BaseTestCase(unittest.TestCase): def setUp(self): self.tempdir = self._make_tempdir() @@ -355,7 +356,7 @@ def callback(*args): f = self._make_temporary(path1)[0] g = self._make_temporary(path1)[0] - from fsevents import Stream, FS_FLAGHISTORYDONE + from fsevents import FS_FLAGHISTORYDONE, Stream stream = Stream(callback, path1, ids = True) @@ -412,7 +413,7 @@ def callback(*args): f = self._make_temporary(path1)[0] g = self._make_temporary(path1)[0] - from fsevents import Stream, FS_CFLAGFILEEVENTS, FS_ITEMISDIR + from fsevents import FS_CFLAGFILEEVENTS, FS_ITEMISDIR, Stream stream = Stream(callback, path1, flags=FS_CFLAGFILEEVENTS) @@ -552,8 +553,7 @@ def callback(event): observer.join() os.unlink(new) - from fsevents import IN_MOVED_FROM - from fsevents import IN_MOVED_TO + from fsevents import IN_MOVED_FROM, IN_MOVED_TO self.assertEqual(len(events), 2) self.assertEqual(events[0].mask, IN_MOVED_FROM) self.assertEqual(events[0].name, os.path.realpath(f.name)) @@ -689,7 +689,8 @@ def callback(event): def test_existing_directories_are_not_reported(self): import os - from fsevents import Stream, Observer + + from fsevents import Observer, Stream events = [] def callback(event): From 3f599d4c94a299d664478ea543e60991bf89e107 Mon Sep 17 00:00:00 2001 From: Malthe Borch Date: Thu, 18 Apr 2024 22:27:23 +0200 Subject: [PATCH 2/4] Fix formatting (black) --- fsevents.py | 106 +++++++++++++------------- pyproject.toml | 3 + setup.py | 91 ++++++++++++----------- tests.py | 196 ++++++++++++++++++++++++++++++++++++++----------- 4 files changed, 261 insertions(+), 135 deletions(-) diff --git a/fsevents.py b/fsevents.py index fbd657a..9bd3161 100644 --- a/fsevents.py +++ b/fsevents.py @@ -4,26 +4,16 @@ import unicodedata from _fsevents import ( - CF_POLLIN, - CF_POLLOUT, - FS_CFLAGFILEEVENTS, - FS_CFLAGIGNORESELF, - FS_CFLAGNODEFER, FS_CFLAGNONE, - FS_CFLAGUSECFTYPES, - FS_CFLAGWATCHROOT, FS_EVENTIDSINCENOW, - FS_FILEEVENTS, FS_FLAGEVENTIDSWRAPPED, FS_FLAGHISTORYDONE, FS_FLAGKERNELDROPPED, FS_FLAGMOUNT, FS_FLAGMUSTSCANSUBDIRS, - FS_FLAGNONE, FS_FLAGROOTCHANGED, FS_FLAGUNMOUNT, FS_FLAGUSERDROPPED, - FS_IGNORESELF, FS_ITEMCHANGEOWNER, FS_ITEMCREATED, FS_ITEMFINDERINFOMOD, @@ -44,27 +34,26 @@ class Mask(int): stringmap = { - FS_FLAGMUSTSCANSUBDIRS: 'MustScanSubDirs', - FS_FLAGUSERDROPPED: 'UserDropped', - FS_FLAGKERNELDROPPED: 'KernelDropped', - FS_FLAGEVENTIDSWRAPPED: 'EventIDsWrapped', - FS_FLAGHISTORYDONE: 'HistoryDone', - FS_FLAGROOTCHANGED: 'RootChanged', - FS_FLAGMOUNT: 'Mount', - FS_FLAGUNMOUNT: 'Unmount', - + FS_FLAGMUSTSCANSUBDIRS: "MustScanSubDirs", + FS_FLAGUSERDROPPED: "UserDropped", + FS_FLAGKERNELDROPPED: "KernelDropped", + FS_FLAGEVENTIDSWRAPPED: "EventIDsWrapped", + FS_FLAGHISTORYDONE: "HistoryDone", + FS_FLAGROOTCHANGED: "RootChanged", + FS_FLAGMOUNT: "Mount", + FS_FLAGUNMOUNT: "Unmount", # Flags when creating the stream. - FS_ITEMCREATED: 'ItemCreated', - FS_ITEMREMOVED: 'ItemRemoved', - FS_ITEMINODEMETAMOD: 'ItemInodeMetaMod', - FS_ITEMRENAMED: 'ItemRenamed', - FS_ITEMMODIFIED: 'ItemModified', - FS_ITEMFINDERINFOMOD: 'ItemFinderInfoMod', - FS_ITEMCHANGEOWNER: 'ItemChangedOwner', - FS_ITEMXATTRMOD: 'ItemXAttrMod', - FS_ITEMISFILE: 'ItemIsFile', - FS_ITEMISDIR: 'ItemIsDir', - FS_ITEMISSYMLINK: 'ItemIsSymlink' + FS_ITEMCREATED: "ItemCreated", + FS_ITEMREMOVED: "ItemRemoved", + FS_ITEMINODEMETAMOD: "ItemInodeMetaMod", + FS_ITEMRENAMED: "ItemRenamed", + FS_ITEMMODIFIED: "ItemModified", + FS_ITEMFINDERINFOMOD: "ItemFinderInfoMod", + FS_ITEMCHANGEOWNER: "ItemChangedOwner", + FS_ITEMXATTRMOD: "ItemXAttrMod", + FS_ITEMISFILE: "ItemIsFile", + FS_ITEMISDIR: "ItemIsDir", + FS_ITEMISSYMLINK: "ItemIsSymlink", } _svals = list(stringmap.items()) @@ -97,7 +86,8 @@ def check_path_string_type(*paths): for path in paths: if not isinstance(path, str): raise TypeError( - "Path must be string, not '%s'." % type(path).__name__) + "Path must be string, not '%s'." % type(path).__name__ + ) class Observer(threading.Thread): @@ -139,16 +129,25 @@ def _schedule(self, stream): if stream.file_events: callback = FileEventCallback(stream.callback, stream.raw_paths) else: + def callback(paths, masks, ids): for path, mask, id in zip(paths, masks, ids): if sys.version_info[0] >= 3: - path = path.decode('utf-8') + path = path.decode("utf-8") if stream.ids is False: stream.callback(path, mask) elif stream.ids is True: stream.callback(path, mask, id) - - schedule(self, stream, callback, stream.paths, stream.since, stream.latency, stream.cflags) + + schedule( + self, + stream, + callback, + stream.paths, + stream.since, + stream.latency, + stream.cflags, + ) def schedule(self, stream): self.lock.acquire() @@ -182,14 +181,17 @@ def stop(self): self.event = None event.set() + class Stream(object): def __init__(self, callback, *paths, **options): - file_events = options.pop('file_events', False) - since = options.pop('since',FS_EVENTIDSINCENOW) - cflags = options.pop('flags', FS_CFLAGNONE) - latency = options.pop('latency', 0.01) - ids = options.pop('ids', False) - assert len(options) == 0, "Invalid option(s): %s" % repr(options.keys()) + file_events = options.pop("file_events", False) + since = options.pop("since", FS_EVENTIDSINCENOW) + cflags = options.pop("flags", FS_CFLAGNONE) + latency = options.pop("latency", 0.01) + ids = options.pop("ids", False) + assert len(options) == 0, "Invalid option(s): %s" % repr( + options.keys() + ) check_path_string_type(*paths) self.callback = callback @@ -197,8 +199,8 @@ def __init__(self, callback, *paths, **options): # The C-extension needs the path in 8-bit form. self.paths = [ - path if isinstance(path, bytes) - else path.encode('utf-8') for path in paths + path if isinstance(path, bytes) else path.encode("utf-8") + for path in paths ] self.file_events = file_events @@ -207,8 +209,9 @@ def __init__(self, callback, *paths, **options): self.latency = latency self.ids = ids + class FileEvent(object): - __slots__ = 'mask', 'cookie', 'name' + __slots__ = "mask", "cookie", "name" def __init__(self, mask, cookie, name): self.mask = mask @@ -218,6 +221,7 @@ def __init__(self, mask, cookie, name): def __repr__(self): return repr((self.mask, self.cookie, self.name)) + class FileEventCallback(object): def __init__(self, callback, paths): self.snapshots = {} @@ -235,13 +239,13 @@ def __call__(self, paths, masks, ids): for path in sorted(paths): # supports UTF-8-MAC(NFD) if not isinstance(path, unicode): - path = path.decode('utf-8') - path = unicodedata.normalize('NFD', path).encode('utf-8') + path = path.decode("utf-8") + path = unicodedata.normalize("NFD", path).encode("utf-8") if sys.version_info[0] >= 3: - path = path.decode('utf-8') - - path = path.rstrip('/') + path = path.decode("utf-8") + + path = path.rstrip("/") snapshot = self.snapshots[path] current = {} try: @@ -266,13 +270,15 @@ def __call__(self, paths, masks, ids): observed.discard(name) else: event = created.get(snap_stat.st_ino) - if (event is not None): + if event is not None: self.cookie += 1 event.mask = IN_MOVED_FROM event.cookie = self.cookie tmp_filename = event.name event.name = filename - events.append(FileEvent(IN_MOVED_TO, self.cookie, tmp_filename)) + events.append( + FileEvent(IN_MOVED_TO, self.cookie, tmp_filename) + ) else: event = FileEvent(IN_DELETE, None, filename) deleted[snap_stat.st_ino] = event diff --git a/pyproject.toml b/pyproject.toml index e93ae64..086ab1b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,2 +1,5 @@ +[tool.black] +line-length = 79 + [tool.isort] multi_line_output=3 diff --git a/setup.py b/setup.py index e6b78c5..c937bcb 100644 --- a/setup.py +++ b/setup.py @@ -9,48 +9,53 @@ def read(fname): with open(os.path.join(os.path.dirname(__file__), fname)) as f: return f.read() + ext_modules = [ - Extension(name = '_fsevents', - sources = ['_fsevents.c', 'compat.c'], - extra_link_args = ["-framework","CoreFoundation", - "-framework","CoreServices"], - ), - ] + Extension( + name="_fsevents", + sources=["_fsevents.c", "compat.c"], + extra_link_args=[ + "-framework", + "CoreFoundation", + "-framework", + "CoreServices", + ], + ), +] -setup(name = "MacFSEvents", - version = "0.8.4", - description = "Thread-based interface to file system observation primitives.", - long_description = "\n\n".join((read('README.rst'), read('CHANGES.rst'))), - license = "BSD", - data_files = [("", [ - "compat.h", - "LICENSE.txt", - "CHANGES.rst" - ])], - author = "Malthe Borch", - author_email = "mborch@gmail.com", - url = 'https://github.com/malthe/macfsevents', - cmdclass = dict(build_ext=build_ext), - ext_modules = ext_modules, - platforms = ["Mac OS X"], - classifiers = [ - 'Development Status :: 4 - Beta', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: BSD License', - 'Operating System :: MacOS :: MacOS X', - 'Programming Language :: C', - 'Programming Language :: Python :: 2.4', - 'Programming Language :: Python :: 2.5', - 'Programming Language :: Python :: 2.6', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Topic :: Software Development :: Libraries :: Python Modules', - 'Topic :: System :: Filesystems', - ], - zip_safe=False, - test_suite="tests", - py_modules=['fsevents'], - ) +setup( + name="MacFSEvents", + version="0.8.4", + description=( + "Thread-based interface to file system observation " "primitives." + ), + long_description="\n\n".join((read("README.rst"), read("CHANGES.rst"))), + license="BSD", + data_files=[("", ["compat.h", "LICENSE.txt", "CHANGES.rst"])], + author="Malthe Borch", + author_email="mborch@gmail.com", + url="https://github.com/malthe/macfsevents", + cmdclass=dict(build_ext=build_ext), + ext_modules=ext_modules, + platforms=["Mac OS X"], + classifiers=[ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "License :: OSI Approved :: BSD License", + "Operating System :: MacOS :: MacOS X", + "Programming Language :: C", + "Programming Language :: Python :: 2.4", + "Programming Language :: Python :: 2.5", + "Programming Language :: Python :: 2.6", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: System :: Filesystems", + ], + zip_safe=False, + test_suite="tests", + py_modules=["fsevents"], +) diff --git a/tests.py b/tests.py index 178e0f5..52f356a 100644 --- a/tests.py +++ b/tests.py @@ -7,21 +7,24 @@ def setUp(self): def tearDown(self): import os + os.rmdir(self.tempdir) def _make_temporary(self, directory=None): import os import tempfile + if directory is None: directory = self.tempdir f = tempfile.NamedTemporaryFile(dir=directory) f.flush() - path = os.path.realpath(os.path.dirname(f.name)).rstrip('/') + '/' + path = os.path.realpath(os.path.dirname(f.name)).rstrip("/") + "/" return f, path def _make_tempdir(self): import os import tempfile + tempdir = tempfile.gettempdir() f = tempfile.NamedTemporaryFile(dir=tempdir) tempdir = os.path.join(tempdir, os.path.basename(f.name)) @@ -29,40 +32,48 @@ def _make_tempdir(self): os.mkdir(tempdir) return tempdir + class PathObservationTestCase(BaseTestCase): @property def modified_mask(self): import fsevents + return ( - fsevents.FS_ITEMCREATED + - fsevents.FS_ITEMMODIFIED + - fsevents.FS_ITEMISFILE + fsevents.FS_ITEMCREATED + + fsevents.FS_ITEMMODIFIED + + fsevents.FS_ITEMISFILE ) + @property def create_and_remove_mask(self): import fsevents + return ( - fsevents.FS_ITEMCREATED + - fsevents.FS_ITEMREMOVED + - fsevents.FS_ITEMISFILE + fsevents.FS_ITEMCREATED + + fsevents.FS_ITEMREMOVED + + fsevents.FS_ITEMISFILE ) - + def test_single_file_added(self): events = [] + def callback(*args): events.append(args) f, path = self._make_temporary() from fsevents import Stream + stream = Stream(callback, path) from fsevents import Observer + observer = Observer() observer.schedule(stream) observer.start() # add single file import time + while not observer.is_alive(): time.sleep(0.1) del events[:] @@ -78,28 +89,33 @@ def callback(*args): def test_multiple_files_added(self): events = [] + def callback(*args): events.append(args) from fsevents import Observer + observer = Observer() from fsevents import Stream + observer.start() # wait until activation import time + while not observer.is_alive(): time.sleep(0.1) time.sleep(0.1) # two files in same directory import os - path1 = os.path.realpath(self._make_tempdir()) + '/' + + path1 = os.path.realpath(self._make_tempdir()) + "/" f = self._make_temporary(path1)[0] g = self._make_temporary(path1)[0] # one file in a separate directory - path2 = os.path.realpath(self._make_tempdir()) + '/' + path2 = os.path.realpath(self._make_tempdir()) + "/" h = self._make_temporary(path2)[0] stream = Stream(callback, path1, path2) @@ -111,7 +127,15 @@ def callback(*args): g.close() h.close() time.sleep(0.2) - self.assertEqual(sorted(events), sorted([(path1, self.create_and_remove_mask), (path2, self.create_and_remove_mask)])) + self.assertEqual( + sorted(events), + sorted( + [ + (path1, self.create_and_remove_mask), + (path2, self.create_and_remove_mask), + ] + ), + ) finally: f.close() g.close() @@ -126,15 +150,18 @@ def callback(*args): def test_single_file_added_multiple_streams(self): events = [] + def callback(*args): events.append(args) f, path = self._make_temporary() from fsevents import Stream + stream1 = Stream(callback, path) stream2 = Stream(callback, path) from fsevents import Observer + observer = Observer() observer.schedule(stream1) observer.schedule(stream2) @@ -142,6 +169,7 @@ def callback(*args): # add single file import time + while not observer.is_alive(): time.sleep(0.1) time.sleep(0.1) @@ -155,21 +183,31 @@ def callback(*args): observer.unschedule(stream2) observer.join() - self.assertEqual(events, [(path, self.create_and_remove_mask), (path, self.create_and_remove_mask)]) + self.assertEqual( + events, + [ + (path, self.create_and_remove_mask), + (path, self.create_and_remove_mask), + ], + ) def test_single_file_added_with_observer_unscheduled(self): events = [] + def callback(*args): events.append(args) f, path = self._make_temporary() from fsevents import Stream + stream = Stream(callback, path) from fsevents import Observer + observer = Observer() observer.start() import time + while not observer.is_alive(): time.sleep(0.1) @@ -189,18 +227,22 @@ def callback(*args): def test_single_file_added_with_observer_rescheduled(self): events = [] + def callback(*args): events.append(args) f, path = self._make_temporary() from fsevents import Stream + stream = Stream(callback, path) from fsevents import Observer + observer = Observer() observer.start() import time + while not observer.is_alive(): time.sleep(0.1) @@ -221,21 +263,28 @@ def callback(*args): def test_single_file_added_to_subdirectory(self): events = [] + def callback(*args): events.append(args) import os + directory = self._make_tempdir() - subdirectory = os.path.realpath(os.path.join(directory, 'subdir')) + '/' + subdirectory = ( + os.path.realpath(os.path.join(directory, "subdir")) + "/" + ) os.mkdir(subdirectory) import time + time.sleep(0.1) try: from fsevents import Stream + stream = Stream(callback, directory) from fsevents import Observer + observer = Observer() observer.schedule(stream) observer.start() @@ -263,20 +312,24 @@ def callback(*args): def test_single_file_added_unschedule_then_stop(self): events = [] + def callback(*args): events.append(args) f, path = self._make_temporary() from fsevents import Stream + stream = Stream(callback, path) from fsevents import Observer + observer = Observer() observer.schedule(stream) observer.start() # add single file import time + while not observer.is_alive(): time.sleep(0.1) del events[:] @@ -292,20 +345,24 @@ def callback(*args): def test_start_then_watch(self): events = [] + def callback(*args): events.append(args) f, path = self._make_temporary() from fsevents import Stream + stream = Stream(callback, path) from fsevents import Observer + observer = Observer() observer.schedule(stream) observer.start() # add single file import time + while not observer.is_alive(): time.sleep(0.1) del events[:] @@ -321,10 +378,12 @@ def callback(*args): def test_start_no_watch(self): events = [] + def callback(*args): events.append(args) from fsevents import Observer + observer = Observer() f, path = self._make_temporary() @@ -332,6 +391,7 @@ def callback(*args): # add single file import time + while not observer.is_alive(): time.sleep(0.1) del events[:] @@ -344,46 +404,50 @@ def callback(*args): self.assertEqual(events, []) -#new cflags and since field tests + # new cflags and since field tests def test_since_stream(self): events = [] + def callback(*args): events.append(args) - + # two files in same directory import os - path1 = os.path.realpath(self._make_tempdir()) + '/' + + path1 = os.path.realpath(self._make_tempdir()) + "/" f = self._make_temporary(path1)[0] g = self._make_temporary(path1)[0] from fsevents import FS_FLAGHISTORYDONE, Stream - - stream = Stream(callback, path1, ids = True) - + + stream = Stream(callback, path1, ids=True) + from fsevents import Observer + observer = Observer() observer.schedule(stream) observer.start() - - #create one file + + # create one file import time + while not observer.is_alive(): time.sleep(0.1) del events[:] f.close() time.sleep(0.2) - + # stop and join observer observer.stop() observer.unschedule(stream) observer.join() - self.assertEqual(len(events),1) + self.assertEqual(len(events), 1) self.assertEqual(events[0][:-1], (path1, self.create_and_remove_mask)) - #create a second file + # create a second file g.close() - - stream = Stream(callback, path1, since = events[0][2]) + + stream = Stream(callback, path1, since=events[0][2]) del events[:] # new observer observer = Observer() @@ -396,75 +460,88 @@ def callback(*args): observer.unschedule(stream) observer.join() - self.assertEqual(len(events),2) - #FIXME: why do events arrive here in reversed order? + self.assertEqual(len(events), 2) + # FIXME: why do events arrive here in reversed order? self.assertEqual(events[1], (path1, self.create_and_remove_mask)) self.assertEqual(events[0], (path1[:-1], FS_FLAGHISTORYDONE)) - def test_fileevent_stream(self): events = [] + def callback(*args): events.append(args) - + # two files in same directory import os - path1 = os.path.realpath(self._make_tempdir()) + '/' + + path1 = os.path.realpath(self._make_tempdir()) + "/" f = self._make_temporary(path1)[0] g = self._make_temporary(path1)[0] from fsevents import FS_CFLAGFILEEVENTS, FS_ITEMISDIR, Stream - + stream = Stream(callback, path1, flags=FS_CFLAGFILEEVENTS) - + from fsevents import Observer + observer = Observer() observer.schedule(stream) observer.start() - - #create two files (here in the same directory) + + # create two files (here in the same directory) import time + while not observer.is_alive(): time.sleep(0.1) del events[:] f.close() g.close() time.sleep(0.2) - + # stop and join observer observer.stop() observer.unschedule(stream) observer.join() import os - self.assertEqual(len(events),3) - self.assertEqual(events, [(path1[:-1], self.create_and_remove_mask|FS_ITEMISDIR), - (f.name, self.create_and_remove_mask), - (g.name, self.create_and_remove_mask)]) - + + self.assertEqual(len(events), 3) + self.assertEqual( + events, + [ + (path1[:-1], self.create_and_remove_mask | FS_ITEMISDIR), + (f.name, self.create_and_remove_mask), + (g.name, self.create_and_remove_mask), + ], + ) class FileObservationTestCase(BaseTestCase): def test_single_file_created(self): events = [] + def callback(event): events.append(event) from fsevents import Stream + stream = Stream(callback, self.tempdir, file_events=True) from fsevents import Observer + observer = Observer() observer.schedule(stream) observer.start() # add single file import time + while not observer.is_alive(): time.sleep(0.1) del events[:] time.sleep(0.1) import os + f = open(os.path.join(self.tempdir, "test"), "w") f.write("abc") f.flush() @@ -478,30 +555,36 @@ def callback(event): os.unlink(f.name) from fsevents import IN_CREATE + self.assertEqual(len(events), 1) self.assertEqual(events[0].mask, IN_CREATE) self.assertEqual(events[0].name, os.path.realpath(f.name)) def test_single_file_deleted(self): events = [] + def callback(event): events.append(event) import os + f = open(os.path.join(self.tempdir, "test"), "w") f.write("abc") f.flush() f.close() from fsevents import Stream + stream = Stream(callback, self.tempdir, file_events=True) from fsevents import Observer + observer = Observer() observer.schedule(stream) observer.start() # add single file import time + while not observer.is_alive(): time.sleep(0.1) del events[:] @@ -515,30 +598,36 @@ def callback(event): observer.join() from fsevents import IN_DELETE + self.assertEqual(len(events), 1) self.assertEqual(events[0].mask, IN_DELETE) self.assertEqual(events[0].name, os.path.realpath(f.name)) def test_single_file_moved(self): events = [] + def callback(event): events.append(event) import os + f = open(os.path.join(self.tempdir, "test"), "w") f.write("abc") f.flush() f.close() from fsevents import Stream + stream = Stream(callback, self.tempdir, file_events=True) from fsevents import Observer + observer = Observer() observer.schedule(stream) observer.start() # add single file import time + while not observer.is_alive(): time.sleep(0.1) del events[:] @@ -554,6 +643,7 @@ def callback(event): os.unlink(new) from fsevents import IN_MOVED_FROM, IN_MOVED_TO + self.assertEqual(len(events), 2) self.assertEqual(events[0].mask, IN_MOVED_FROM) self.assertEqual(events[0].name, os.path.realpath(f.name)) @@ -563,23 +653,28 @@ def callback(event): def test_single_file_modified(self): events = [] + def callback(event): events.append(event) import os + f = open(os.path.join(self.tempdir, "test"), "w") f.write("abc") f.flush() from fsevents import Stream + stream = Stream(callback, self.tempdir, file_events=True) from fsevents import Observer + observer = Observer() observer.schedule(stream) observer.start() # add single file import time + while not observer.is_alive(): time.sleep(0.1) del events[:] @@ -596,31 +691,37 @@ def callback(event): os.unlink(f.name) from fsevents import IN_MODIFY + self.assertEqual(len(events), 1) self.assertEqual(events[0].mask, IN_MODIFY) self.assertEqual(events[0].name, os.path.realpath(f.name)) def test_single_file_created_and_modified(self): events = [] + def callback(event): events.append(event) from fsevents import Stream + stream = Stream(callback, self.tempdir, file_events=True) from fsevents import Observer + observer = Observer() observer.schedule(stream) observer.start() # add single file import time + while not observer.is_alive(): time.sleep(0.1) del events[:] time.sleep(2.1) import os + f = open(os.path.join(self.tempdir, "test"), "w") f.write("abc") f.flush() @@ -639,6 +740,7 @@ def callback(event): os.unlink(f.name) from fsevents import IN_CREATE, IN_MODIFY + self.assertEqual(len(events), 2) self.assertEqual(events[0].mask, IN_CREATE) self.assertEqual(events[0].name, os.path.realpath(f.name)) @@ -647,27 +749,33 @@ def callback(event): def test_single_directory_deleted(self): events = [] + def callback(event): events.append(event) import os + new1 = os.path.join(self.tempdir, "newdir1") new2 = os.path.join(self.tempdir, "newdir2") try: os.mkdir(new1) os.mkdir(new2) import time + time.sleep(0.2) from fsevents import Stream + stream = Stream(callback, self.tempdir, file_events=True) from fsevents import Observer + observer = Observer() observer.schedule(stream) observer.start() # add single file import time + while not observer.is_alive(): time.sleep(0.1) del events[:] @@ -681,6 +789,7 @@ def callback(event): observer.join() from fsevents import IN_DELETE + self.assertEqual(len(events), 1) self.assertEqual(events[0].mask, IN_DELETE) self.assertEqual(events[0].name, os.path.realpath(new2)) @@ -693,9 +802,10 @@ def test_existing_directories_are_not_reported(self): from fsevents import Observer, Stream events = [] + def callback(event): events.append(event) - + stream = Stream(callback, self.tempdir, file_events=True) new1 = os.path.join(self.tempdir, "newdir1") new2 = os.path.join(self.tempdir, "newdir2") @@ -705,6 +815,7 @@ def callback(event): observer.start() import time + while not observer.is_alive(): time.sleep(0.1) del events[:] @@ -717,6 +828,7 @@ def callback(event): observer.join() from fsevents import IN_CREATE + self.assertEqual(len(events), 1) self.assertEqual(events[0].mask, IN_CREATE) self.assertEqual(events[0].name, os.path.realpath(new2)) From 7bd5a33154bdb46f179e51ef579e14aa96f83e1c Mon Sep 17 00:00:00 2001 From: Malthe Borch Date: Thu, 18 Apr 2024 22:27:43 +0200 Subject: [PATCH 3/4] Add tox configuration to test across environments --- setup.py | 7 ++----- tox.ini | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 5 deletions(-) create mode 100644 tox.ini diff --git a/setup.py b/setup.py index c937bcb..750fea9 100644 --- a/setup.py +++ b/setup.py @@ -44,14 +44,11 @@ def read(fname): "License :: OSI Approved :: BSD License", "Operating System :: MacOS :: MacOS X", "Programming Language :: C", - "Programming Language :: Python :: 2.4", - "Programming Language :: Python :: 2.5", - "Programming Language :: Python :: 2.6", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: System :: Filesystems", ], diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..45830a8 --- /dev/null +++ b/tox.ini @@ -0,0 +1,39 @@ +[tox] +minversion = 3.18 +envlist = + lint + py38 + py39 + py310 + py311 + py312 + +[testenv] +usedevelop = true +deps = + build + wheel + pytest + setuptools +commands = + python -m unittest -v +extras = + test +setenv = + LOGNAME=dummy + +[testenv:lint] +basepython = python3 +skip_install = true +commands = + isort --check-only --diff . + flake8 *.py + check-manifest + check-python-versions +deps = + check-manifest + check-python-versions >= 0.19.1 + wheel + flake8 + isort + From cd11299d839ee4f99c2cada3833870b58906084a Mon Sep 17 00:00:00 2001 From: Malthe Borch Date: Thu, 18 Apr 2024 22:29:13 +0200 Subject: [PATCH 4/4] Add GitHub workflow to build distribution artifacts and run automated tests --- .github/workflows/main.yml | 57 ++++++++++++++++++++++++++++++++++++++ .travis.yml | 18 ------------ MANIFEST.in | 2 ++ fsevents.py | 30 ++++++++++++++++++++ tox.ini | 2 +- 5 files changed, 90 insertions(+), 19 deletions(-) create mode 100644 .github/workflows/main.yml delete mode 100644 .travis.yml create mode 100644 MANIFEST.in diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..844eb77 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,57 @@ +on: + push: + pull_request: + # Allow to run this workflow manually from the Actions tab + workflow_dispatch: + +jobs: + build: + runs-on: macos-latest + steps: + - uses: actions/checkout@v3 + - run: python -m pip install setuptools wheel + - run: python setup.py sdist + - run: python setup.py bdist_wheel + - uses: actions/upload-artifact@v4 + with: + name: MacFSEvents + path: ./dist/ + test: + strategy: + # We want to see all failures: + fail-fast: false + matrix: + config: + - ["3.8", "py38", "macos-12"] + - ["3.9", "py38", "macos-12"] + - ["3.9", "lint", "macos-12"] + - ["3.9", "py39", "macos-12"] + - ["3.10", "py310", "macos-12"] + - ["3.11", "py311", "macos-12"] + - ["3.12", "py312", "macos-12"] + - ["3.12", "py312", "macos-13"] + - ["3.12", "py312", "macos-14"] + runs-on: ${{ matrix.config[2] }} + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name + name: ${{ matrix.config[0] }}-${{ matrix.config[1] }}-${{ matrix.config[2] }} + steps: + - run: git config --global core.autocrlf false + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.config[0] }} + - name: Pip cache + uses: actions/cache@v3 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ matrix.config[0] }}-${{ hashFiles('setup.*', 'tox.ini') }} + restore-keys: | + ${{ runner.os }}-pip-${{ matrix.config[0] }}- + ${{ runner.os }}-pip- + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install tox + - name: Test + run: tox -e ${{ matrix.config[1] }} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index a85cd59..0000000 --- a/.travis.yml +++ /dev/null @@ -1,18 +0,0 @@ -sudo: false -osx_image: xcode7.1 - -language: objective-c - -before_install: -- brew install python3 - -script: -- python setup.py build -- python setup.py test -- python3 setup.py build -- python3 setup.py test - -notifications: - email: - on_success: never - on_failure: change diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..a1ccdad --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +include *.py +include tox.ini diff --git a/fsevents.py b/fsevents.py index 9bd3161..ff874d6 100644 --- a/fsevents.py +++ b/fsevents.py @@ -4,6 +4,7 @@ import unicodedata from _fsevents import ( + FS_CFLAGFILEEVENTS, FS_CFLAGNONE, FS_EVENTIDSINCENOW, FS_FLAGEVENTIDSWRAPPED, @@ -320,3 +321,32 @@ def snapshot(self, path): entry[obj] = os.lstat(os.path.join(root, obj)) except OSError: continue + + +__all__ = ( + FS_CFLAGFILEEVENTS, + FS_CFLAGNONE, + FS_EVENTIDSINCENOW, + FS_FLAGEVENTIDSWRAPPED, + FS_FLAGHISTORYDONE, + FS_FLAGKERNELDROPPED, + FS_FLAGMOUNT, + FS_FLAGMUSTSCANSUBDIRS, + FS_FLAGROOTCHANGED, + FS_FLAGUNMOUNT, + FS_FLAGUSERDROPPED, + FS_ITEMCHANGEOWNER, + FS_ITEMCREATED, + FS_ITEMFINDERINFOMOD, + FS_ITEMINODEMETAMOD, + FS_ITEMISDIR, + FS_ITEMISFILE, + FS_ITEMISSYMLINK, + FS_ITEMMODIFIED, + FS_ITEMREMOVED, + FS_ITEMRENAMED, + FS_ITEMXATTRMOD, + FileEvent, + Stream, + Observer, +) diff --git a/tox.ini b/tox.ini index 45830a8..c4af272 100644 --- a/tox.ini +++ b/tox.ini @@ -27,7 +27,7 @@ basepython = python3 skip_install = true commands = isort --check-only --diff . - flake8 *.py + flake8 -v tests.py fsevents.py check-manifest check-python-versions deps =