Skip to content

Commit 38a69fd

Browse files
Report the type of change observed in FileWatcher
Previously, the FileWatcher only provided information of the path of the changed file, without indicating whether the change was a creation, modification, or deletion. This commit improves the functionality of the FileWatcher by adding the ability to report the type of change observed. This additional information can be useful when tracking changes to files and understanding the context of the change. Signed-off-by: Daniel Zullo <[email protected]>
1 parent 8251a60 commit 38a69fd

File tree

4 files changed

+34
-10
lines changed

4 files changed

+34
-10
lines changed

RELEASE_NOTES.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,19 @@ This release adds support to pass `None` values via channels and revamps the `Ti
5353

5454
Therefore, you should now check a file receiving an event really exist before trying to operate on it.
5555

56+
* `FileWatcher` reports the type of event observed in addition to the file path.
57+
58+
Previously, only the file path was reported. With this update, users can now determine if a file change is a creation, modification, or deletion.
59+
Note that this change may require updates to your existing code that relies on `FileWatcher` as the new interface returns a `FileWatcher.Event` instead of just the file path.
60+
5661
## New Features
5762

5863
* `util.Timer` was replaced by a more generic implementation that allows for customizable policies to handle missed ticks.
5964

6065
* Passing `None` values via channels is now supported.
6166

67+
* `FileWatcher.Event` was added to notify events when a file is created, modified, or deleted.
68+
6269
## Bug Fixes
6370

6471
* `util.Select` / `util.Merge` / `util.MergeNamed`: Cancel pending tasks in `__del__` methods only if possible (the loop is not already closed).

src/frequenz/channels/util/_file_watcher.py

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import asyncio
99
import pathlib
10+
from dataclasses import dataclass
1011
from enum import Enum
1112

1213
from watchfiles import Change, awatch
@@ -16,7 +17,7 @@
1617
from .._exceptions import ReceiverStoppedError
1718

1819

19-
class FileWatcher(Receiver[pathlib.Path]):
20+
class FileWatcher(Receiver["FileWatcher.Event"]):
2021
"""A channel receiver that watches for file events."""
2122

2223
class EventType(Enum):
@@ -26,6 +27,15 @@ class EventType(Enum):
2627
MODIFY = Change.modified
2728
DELETE = Change.deleted
2829

30+
@dataclass(frozen=True)
31+
class Event:
32+
"""A file change event."""
33+
34+
type: FileWatcher.EventType
35+
"""The type of change that was observed."""
36+
path: pathlib.Path
37+
"""The path where the change was observed."""
38+
2939
def __init__(
3040
self,
3141
paths: list[pathlib.Path | str],
@@ -104,11 +114,11 @@ async def ready(self) -> bool:
104114

105115
return True
106116

107-
def consume(self) -> pathlib.Path:
108-
"""Return the latest change once `ready` is complete.
117+
def consume(self) -> Event:
118+
"""Return the latest event once `ready` is complete.
109119
110120
Returns:
111-
The next change that was received.
121+
The next event that was received.
112122
113123
Raises:
114124
ReceiverStoppedError: if there is some problem with the receiver.
@@ -117,8 +127,8 @@ def consume(self) -> pathlib.Path:
117127
raise ReceiverStoppedError(self) from self._awatch_stopped_exc
118128

119129
assert self._changes, "`consume()` must be preceeded by a call to `ready()`"
120-
change = self._changes.pop()
121130
# Tuple of (Change, path) returned by watchfiles
122-
_, path_str = change
123-
path = pathlib.Path(path_str)
124-
return path
131+
change, path_str = self._changes.pop()
132+
return FileWatcher.Event(
133+
type=FileWatcher.EventType(change), path=pathlib.Path(path_str)
134+
)

tests/utils/test_file_watcher.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,9 @@ async def test_file_watcher_receive_updates(
7171

7272
for change in changes:
7373
recv_changes = await file_watcher.receive()
74-
assert recv_changes == pathlib.Path(change[1])
74+
event_type = FileWatcher.EventType(change[0])
75+
path = pathlib.Path(change[1])
76+
assert recv_changes == FileWatcher.Event(type=event_type, path=path)
7577

7678

7779
@hypothesis.given(event_types=st.sets(st.sampled_from(FileWatcher.EventType)))

tests/utils/test_integration.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,12 @@ async def test_file_watcher(tmp_path: pathlib.Path) -> None:
3636
if msg := select.timer:
3737
filename.write_text(f"{msg.inner}")
3838
elif msg := select.file_watcher:
39-
assert msg.inner == filename
39+
event_type = (
40+
FileWatcher.EventType.CREATE
41+
if number_of_writes == 0
42+
else FileWatcher.EventType.MODIFY
43+
)
44+
assert msg.inner == FileWatcher.Event(type=event_type, path=filename)
4045
number_of_writes += 1
4146
# After receiving a write 3 times, unsubscribe from the writes channel
4247
if number_of_writes == expected_number_of_writes:

0 commit comments

Comments
 (0)