memowatch 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- memowatch-0.1.0/PKG-INFO +163 -0
- memowatch-0.1.0/README.md +135 -0
- memowatch-0.1.0/memowatch/__init__.py +99 -0
- memowatch-0.1.0/memowatch/backends/__init__.py +3 -0
- memowatch-0.1.0/memowatch/backends/base.py +78 -0
- memowatch-0.1.0/memowatch/backends/linux.py +259 -0
- memowatch-0.1.0/memowatch/backends/polling.py +156 -0
- memowatch-0.1.0/memowatch/backends/win32.py +377 -0
- memowatch-0.1.0/memowatch/cli/__init__.py +3 -0
- memowatch-0.1.0/memowatch/cli/main.py +218 -0
- memowatch-0.1.0/memowatch/core/__init__.py +3 -0
- memowatch-0.1.0/memowatch/core/ast_diff.py +108 -0
- memowatch-0.1.0/memowatch/core/diff.py +125 -0
- memowatch-0.1.0/memowatch/core/events.py +259 -0
- memowatch-0.1.0/memowatch/core/observer.py +264 -0
- memowatch-0.1.0/memowatch/core/pipeline.py +296 -0
- memowatch-0.1.0/memowatch/core/predict.py +74 -0
- memowatch-0.1.0/memowatch/filters/__init__.py +3 -0
- memowatch-0.1.0/memowatch/filters/gitignore.py +255 -0
- memowatch-0.1.0/memowatch/filters/pattern.py +66 -0
- memowatch-0.1.0/memowatch/filters/size_mime.py +61 -0
- memowatch-0.1.0/memowatch/fluent.py +133 -0
- memowatch-0.1.0/memowatch/utils/__init__.py +3 -0
- memowatch-0.1.0/memowatch/utils/hasher.py +61 -0
- memowatch-0.1.0/memowatch/utils/lock_check.py +52 -0
- memowatch-0.1.0/memowatch.egg-info/PKG-INFO +163 -0
- memowatch-0.1.0/memowatch.egg-info/SOURCES.txt +34 -0
- memowatch-0.1.0/memowatch.egg-info/dependency_links.txt +1 -0
- memowatch-0.1.0/memowatch.egg-info/entry_points.txt +2 -0
- memowatch-0.1.0/memowatch.egg-info/requires.txt +4 -0
- memowatch-0.1.0/memowatch.egg-info/top_level.txt +1 -0
- memowatch-0.1.0/pyproject.toml +50 -0
- memowatch-0.1.0/setup.cfg +4 -0
- memowatch-0.1.0/tests/test_events.py +46 -0
- memowatch-0.1.0/tests/test_filters.py +49 -0
- memowatch-0.1.0/tests/test_pipeline.py +75 -0
memowatch-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: memowatch
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A modern, high-performance filesystem monitoring library for Python with async support, event debouncing, file settling detection, and content-aware diffing.
|
|
5
|
+
Author-email: Yogesh V <yogeshgokul372@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/yogesh12s/memowatch
|
|
8
|
+
Project-URL: Documentation, https://github.com/yogesh12s/memowatch#readme
|
|
9
|
+
Project-URL: Issues, https://github.com/yogesh12s/memowatch/issues
|
|
10
|
+
Keywords: filesystem,monitoring,watchdog,inotify,fsevents,async
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: System :: Filesystems
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Requires-Python: >=3.8
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
27
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
|
|
28
|
+
|
|
29
|
+
<div align="center">
|
|
30
|
+
|
|
31
|
+
# ๐ญ memowatch
|
|
32
|
+
**The file watcher from the year 2042.**
|
|
33
|
+
|
|
34
|
+
[](#)
|
|
35
|
+
[](https://pypi.org/project/memowatch/)
|
|
36
|
+
[](https://opensource.org/licenses/MIT)
|
|
37
|
+
[](#)
|
|
38
|
+
[](#)
|
|
39
|
+
[](https://www.buymeacoffee.com/yogeshgokul)
|
|
40
|
+
|
|
41
|
+
*Because knowing a file changed is cool, but knowing your code logic changed before you even hit 'save' is cooler.* ๐
|
|
42
|
+
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## ๐ฝ What is this?
|
|
48
|
+
|
|
49
|
+
Look, standard file watchers (we love you, `watchdog` โค๏ธ) are great if you want to be bombarded with 17 rapid-fire `FileModifiedEvent`s every time you hit `Ctrl+S` in your editor.
|
|
50
|
+
|
|
51
|
+
**`memowatch`** is a high-performance, async-first Python file watcher that actually uses its brain. It debounces events, reads your `.gitignore`, natively speaks Windows & Linux C-APIs without clunky dependencies, andโwait for itโ**understands your code**.
|
|
52
|
+
|
|
53
|
+
Oh, and it predicts the future. Seriously.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## โจ Features That Will Make You Cry Tears of Joy
|
|
58
|
+
|
|
59
|
+
| ๐ฆธโโ๏ธ Superpower | ๐ What it actually does |
|
|
60
|
+
| :--- | :--- |
|
|
61
|
+
| **AST-Aware Code Watcher** ๐งฌ | Don't just watch files. Watch *logic*. Triggers on `FunctionAdded` or `ClassModified` while totally ignoring that extra space you accidentally typed. |
|
|
62
|
+
| **Predictive Pre-Fetching** ๐ฎ | A built-in Markov Chain engine learns your habits and screams *"Hey, they're probably gonna edit `views.py` next!"* |
|
|
63
|
+
| **Fluent "Reads-like-English" API** ๐ฃ๏ธ | Configure your watcher using a Builder pattern that reads so smoothly your non-coder boss might understand it. |
|
|
64
|
+
| **Event Debouncing** ๐ | Collapses the 5 events your IDE generates for a single save into **one** logical change. |
|
|
65
|
+
| **File Settling Detector** ๐ | `FileSettledEvent` waits for your giant 10GB video file to *actually finish downloading* and release its file locks before telling you to process it. |
|
|
66
|
+
| **Content Hashing** ๐ต๏ธโโ๏ธ | SHA-256 diffing means we ignore files if you just updated the timestamp but didn't change the text. |
|
|
67
|
+
| **Native `.gitignore`** ๐ | It automatically ignores your `__pycache__` and `node_modules` so you don't have to write an exclusion regex. |
|
|
68
|
+
| **Pure Python Win32 & Inotify** ๐ฅ๏ธ | Zero C-extensions to compile. Zero `pywin32` dependencies. Just pure, unadulterated `ctypes` magic. |
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## ๐ Quick Start (0 to 100 real quick)
|
|
73
|
+
|
|
74
|
+
### Installation
|
|
75
|
+
*(Requires Python 3.8+ because we have standards)*
|
|
76
|
+
```bash
|
|
77
|
+
pip install memowatch
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### ๐ฃ๏ธ The "Reads Like English" Fluent API
|
|
81
|
+
Why write boilerplate classes when you can write poetry?
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
import memowatch
|
|
85
|
+
|
|
86
|
+
def freak_out(event):
|
|
87
|
+
print(f"๐จ Whoa! You changed a function named: {event.node_name}")
|
|
88
|
+
|
|
89
|
+
# Reads like plain English!
|
|
90
|
+
watcher = (
|
|
91
|
+
memowatch.observe("./src")
|
|
92
|
+
.ignoring_directories()
|
|
93
|
+
.when_code_changes()
|
|
94
|
+
.specifically("FunctionAdded", "ClassModified")
|
|
95
|
+
.then_execute(freak_out)
|
|
96
|
+
.with_predictive_fetching()
|
|
97
|
+
.start_in_background()
|
|
98
|
+
)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### ๐ฆ The "I just want callbacks" Sync API
|
|
102
|
+
For the traditionalists among us.
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
import memowatch
|
|
106
|
+
|
|
107
|
+
class MyChillHandler(memowatch.FileSystemEventHandler):
|
|
108
|
+
def on_settled(self, event):
|
|
109
|
+
print(f"๐ Ahhh, {event.src_path} is finally done writing.")
|
|
110
|
+
|
|
111
|
+
observer = memowatch.Observer(debounce_ms=300, settle_timeout_ms=1000)
|
|
112
|
+
observer.schedule(MyChillHandler(), path="./my_project", recursive=True)
|
|
113
|
+
observer.start()
|
|
114
|
+
|
|
115
|
+
# Keep the main thread alive, grab a coffee โ
|
|
116
|
+
import time
|
|
117
|
+
try:
|
|
118
|
+
while True: time.sleep(1)
|
|
119
|
+
except KeyboardInterrupt:
|
|
120
|
+
observer.stop()
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## ๐ฅ๏ธ The Badass CLI
|
|
126
|
+
|
|
127
|
+
Don't want to write code? Use the built-in CLI daemon.
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
# Watch a directory and print events like a normal person
|
|
131
|
+
memowatch watch ./src
|
|
132
|
+
|
|
133
|
+
# Run a daemon watcher in the background and trigger a build script
|
|
134
|
+
memowatch watch ./src --background --run "python build.py"
|
|
135
|
+
|
|
136
|
+
# Stop the invisible background watcher before it becomes self-aware
|
|
137
|
+
memowatch stop ./src
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## ๐ The Glossary of Events
|
|
143
|
+
|
|
144
|
+
If it happens on your hard drive, we have an event for it:
|
|
145
|
+
|
|
146
|
+
| Event Class | Why you care |
|
|
147
|
+
| :--- | :--- |
|
|
148
|
+
| `FileCreatedEvent` | A new file appeared out of thin air. |
|
|
149
|
+
| `FileSettledEvent` | **(Special โจ)** The file is completely finished writing and the locks are gone. |
|
|
150
|
+
| `FileDiffEvent` | **(Special โจ)** Tells you the *exact* lines added or removed. No `git status` required. |
|
|
151
|
+
| `CodeChangedEvent` | **(Special โจ)** You added/modified a Python function or class. |
|
|
152
|
+
| `FilePredictedEvent` | **(Special โจ)** The AI thinks you're about to open this file. |
|
|
153
|
+
| `FileDeletedEvent` | It's gone. Poof. |
|
|
154
|
+
| `FileMovedEvent` | Witness protection program for files. |
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
<div align="center">
|
|
159
|
+
Made with โค๏ธ and way too much caffeine by <a href="https://github.com/yogesh12s">Yogesh Gokul</a>.<br>
|
|
160
|
+
<sub>(Drop a โญ if you think file watching should be this fun!)</sub><br><br>
|
|
161
|
+
|
|
162
|
+
<a href="https://www.buymeacoffee.com/yogeshgokul" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 40px !important;width: 145px !important;" ></a>
|
|
163
|
+
</div>
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# ๐ญ memowatch
|
|
4
|
+
**The file watcher from the year 2042.**
|
|
5
|
+
|
|
6
|
+
[](#)
|
|
7
|
+
[](https://pypi.org/project/memowatch/)
|
|
8
|
+
[](https://opensource.org/licenses/MIT)
|
|
9
|
+
[](#)
|
|
10
|
+
[](#)
|
|
11
|
+
[](https://www.buymeacoffee.com/yogeshgokul)
|
|
12
|
+
|
|
13
|
+
*Because knowing a file changed is cool, but knowing your code logic changed before you even hit 'save' is cooler.* ๐
|
|
14
|
+
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## ๐ฝ What is this?
|
|
20
|
+
|
|
21
|
+
Look, standard file watchers (we love you, `watchdog` โค๏ธ) are great if you want to be bombarded with 17 rapid-fire `FileModifiedEvent`s every time you hit `Ctrl+S` in your editor.
|
|
22
|
+
|
|
23
|
+
**`memowatch`** is a high-performance, async-first Python file watcher that actually uses its brain. It debounces events, reads your `.gitignore`, natively speaks Windows & Linux C-APIs without clunky dependencies, andโwait for itโ**understands your code**.
|
|
24
|
+
|
|
25
|
+
Oh, and it predicts the future. Seriously.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## โจ Features That Will Make You Cry Tears of Joy
|
|
30
|
+
|
|
31
|
+
| ๐ฆธโโ๏ธ Superpower | ๐ What it actually does |
|
|
32
|
+
| :--- | :--- |
|
|
33
|
+
| **AST-Aware Code Watcher** ๐งฌ | Don't just watch files. Watch *logic*. Triggers on `FunctionAdded` or `ClassModified` while totally ignoring that extra space you accidentally typed. |
|
|
34
|
+
| **Predictive Pre-Fetching** ๐ฎ | A built-in Markov Chain engine learns your habits and screams *"Hey, they're probably gonna edit `views.py` next!"* |
|
|
35
|
+
| **Fluent "Reads-like-English" API** ๐ฃ๏ธ | Configure your watcher using a Builder pattern that reads so smoothly your non-coder boss might understand it. |
|
|
36
|
+
| **Event Debouncing** ๐ | Collapses the 5 events your IDE generates for a single save into **one** logical change. |
|
|
37
|
+
| **File Settling Detector** ๐ | `FileSettledEvent` waits for your giant 10GB video file to *actually finish downloading* and release its file locks before telling you to process it. |
|
|
38
|
+
| **Content Hashing** ๐ต๏ธโโ๏ธ | SHA-256 diffing means we ignore files if you just updated the timestamp but didn't change the text. |
|
|
39
|
+
| **Native `.gitignore`** ๐ | It automatically ignores your `__pycache__` and `node_modules` so you don't have to write an exclusion regex. |
|
|
40
|
+
| **Pure Python Win32 & Inotify** ๐ฅ๏ธ | Zero C-extensions to compile. Zero `pywin32` dependencies. Just pure, unadulterated `ctypes` magic. |
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## ๐ Quick Start (0 to 100 real quick)
|
|
45
|
+
|
|
46
|
+
### Installation
|
|
47
|
+
*(Requires Python 3.8+ because we have standards)*
|
|
48
|
+
```bash
|
|
49
|
+
pip install memowatch
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### ๐ฃ๏ธ The "Reads Like English" Fluent API
|
|
53
|
+
Why write boilerplate classes when you can write poetry?
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
import memowatch
|
|
57
|
+
|
|
58
|
+
def freak_out(event):
|
|
59
|
+
print(f"๐จ Whoa! You changed a function named: {event.node_name}")
|
|
60
|
+
|
|
61
|
+
# Reads like plain English!
|
|
62
|
+
watcher = (
|
|
63
|
+
memowatch.observe("./src")
|
|
64
|
+
.ignoring_directories()
|
|
65
|
+
.when_code_changes()
|
|
66
|
+
.specifically("FunctionAdded", "ClassModified")
|
|
67
|
+
.then_execute(freak_out)
|
|
68
|
+
.with_predictive_fetching()
|
|
69
|
+
.start_in_background()
|
|
70
|
+
)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### ๐ฆ The "I just want callbacks" Sync API
|
|
74
|
+
For the traditionalists among us.
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
import memowatch
|
|
78
|
+
|
|
79
|
+
class MyChillHandler(memowatch.FileSystemEventHandler):
|
|
80
|
+
def on_settled(self, event):
|
|
81
|
+
print(f"๐ Ahhh, {event.src_path} is finally done writing.")
|
|
82
|
+
|
|
83
|
+
observer = memowatch.Observer(debounce_ms=300, settle_timeout_ms=1000)
|
|
84
|
+
observer.schedule(MyChillHandler(), path="./my_project", recursive=True)
|
|
85
|
+
observer.start()
|
|
86
|
+
|
|
87
|
+
# Keep the main thread alive, grab a coffee โ
|
|
88
|
+
import time
|
|
89
|
+
try:
|
|
90
|
+
while True: time.sleep(1)
|
|
91
|
+
except KeyboardInterrupt:
|
|
92
|
+
observer.stop()
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## ๐ฅ๏ธ The Badass CLI
|
|
98
|
+
|
|
99
|
+
Don't want to write code? Use the built-in CLI daemon.
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
# Watch a directory and print events like a normal person
|
|
103
|
+
memowatch watch ./src
|
|
104
|
+
|
|
105
|
+
# Run a daemon watcher in the background and trigger a build script
|
|
106
|
+
memowatch watch ./src --background --run "python build.py"
|
|
107
|
+
|
|
108
|
+
# Stop the invisible background watcher before it becomes self-aware
|
|
109
|
+
memowatch stop ./src
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## ๐ The Glossary of Events
|
|
115
|
+
|
|
116
|
+
If it happens on your hard drive, we have an event for it:
|
|
117
|
+
|
|
118
|
+
| Event Class | Why you care |
|
|
119
|
+
| :--- | :--- |
|
|
120
|
+
| `FileCreatedEvent` | A new file appeared out of thin air. |
|
|
121
|
+
| `FileSettledEvent` | **(Special โจ)** The file is completely finished writing and the locks are gone. |
|
|
122
|
+
| `FileDiffEvent` | **(Special โจ)** Tells you the *exact* lines added or removed. No `git status` required. |
|
|
123
|
+
| `CodeChangedEvent` | **(Special โจ)** You added/modified a Python function or class. |
|
|
124
|
+
| `FilePredictedEvent` | **(Special โจ)** The AI thinks you're about to open this file. |
|
|
125
|
+
| `FileDeletedEvent` | It's gone. Poof. |
|
|
126
|
+
| `FileMovedEvent` | Witness protection program for files. |
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
<div align="center">
|
|
131
|
+
Made with โค๏ธ and way too much caffeine by <a href="https://github.com/yogesh12s">Yogesh Gokul</a>.<br>
|
|
132
|
+
<sub>(Drop a โญ if you think file watching should be this fun!)</sub><br><br>
|
|
133
|
+
|
|
134
|
+
<a href="https://www.buymeacoffee.com/yogeshgokul" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 40px !important;width: 145px !important;" ></a>
|
|
135
|
+
</div>
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""
|
|
2
|
+
memowatch - A modern, high-performance filesystem monitoring library for Python.
|
|
3
|
+
|
|
4
|
+
Features beyond standard watchdog:
|
|
5
|
+
- First-class async/await support (async iterators)
|
|
6
|
+
- Built-in event debouncing & coalescing
|
|
7
|
+
- File settling / lock detection (FileSettledEvent)
|
|
8
|
+
- Content-aware SHA-256 diffing
|
|
9
|
+
- Native .gitignore parser + glob/regex/size filters
|
|
10
|
+
- Pure-Python Windows backend via ctypes (no pywin32)
|
|
11
|
+
- CLI with command runner and JSON streaming
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
# Sync / callback API
|
|
15
|
+
import memowatch
|
|
16
|
+
|
|
17
|
+
class MyHandler(memowatch.FileSystemEventHandler):
|
|
18
|
+
def on_created(self, event):
|
|
19
|
+
print(f"Created: {event.src_path}")
|
|
20
|
+
|
|
21
|
+
observer = memowatch.Observer()
|
|
22
|
+
observer.schedule(MyHandler(), path="./src", recursive=True)
|
|
23
|
+
observer.start()
|
|
24
|
+
|
|
25
|
+
# Async API
|
|
26
|
+
import asyncio
|
|
27
|
+
|
|
28
|
+
async def main():
|
|
29
|
+
async with memowatch.async_watch("./src") as stream:
|
|
30
|
+
async for event in stream:
|
|
31
|
+
print(event)
|
|
32
|
+
|
|
33
|
+
asyncio.run(main())
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
__version__ = "0.1.0"
|
|
37
|
+
__author__ = "Yogesh Gokul <yogeshgokul372@gmail.com>"
|
|
38
|
+
|
|
39
|
+
# Display author information on import as requested
|
|
40
|
+
print(f"Loaded memowatch v{__version__} - Built with โค๏ธ by {__author__}")
|
|
41
|
+
|
|
42
|
+
from memowatch.core.events import (
|
|
43
|
+
FileSystemEvent,
|
|
44
|
+
FileCreatedEvent,
|
|
45
|
+
FileModifiedEvent,
|
|
46
|
+
FileDeletedEvent,
|
|
47
|
+
FileMovedEvent,
|
|
48
|
+
FileSettledEvent,
|
|
49
|
+
DirCreatedEvent,
|
|
50
|
+
DirModifiedEvent,
|
|
51
|
+
DirDeletedEvent,
|
|
52
|
+
DirMovedEvent,
|
|
53
|
+
EVENT_TYPE_CREATED,
|
|
54
|
+
EVENT_TYPE_MODIFIED,
|
|
55
|
+
EVENT_TYPE_DELETED,
|
|
56
|
+
EVENT_TYPE_MOVED,
|
|
57
|
+
EVENT_TYPE_SETTLED,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
from memowatch.core.observer import (
|
|
61
|
+
FileSystemEventHandler,
|
|
62
|
+
Observer,
|
|
63
|
+
async_watch,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
from memowatch.fluent import observe
|
|
67
|
+
|
|
68
|
+
__all__ = [
|
|
69
|
+
# Version
|
|
70
|
+
"__version__",
|
|
71
|
+
# Events
|
|
72
|
+
"FileSystemEvent",
|
|
73
|
+
"FileCreatedEvent",
|
|
74
|
+
"FileModifiedEvent",
|
|
75
|
+
"FileDeletedEvent",
|
|
76
|
+
"FileMovedEvent",
|
|
77
|
+
"FileSettledEvent",
|
|
78
|
+
"CodeChangedEvent",
|
|
79
|
+
"FilePredictedEvent",
|
|
80
|
+
"FileDiffEvent",
|
|
81
|
+
"DirCreatedEvent",
|
|
82
|
+
"DirModifiedEvent",
|
|
83
|
+
"DirDeletedEvent",
|
|
84
|
+
"DirMovedEvent",
|
|
85
|
+
"EVENT_TYPE_CREATED",
|
|
86
|
+
"EVENT_TYPE_MODIFIED",
|
|
87
|
+
"EVENT_TYPE_DELETED",
|
|
88
|
+
"EVENT_TYPE_MOVED",
|
|
89
|
+
"EVENT_TYPE_SETTLED",
|
|
90
|
+
"EVENT_TYPE_DIFF",
|
|
91
|
+
"EVENT_TYPE_CODE_CHANGED",
|
|
92
|
+
"EVENT_TYPE_PREDICTED",
|
|
93
|
+
# Observer
|
|
94
|
+
"FileSystemEventHandler",
|
|
95
|
+
"Observer",
|
|
96
|
+
"async_watch",
|
|
97
|
+
# Fluent API
|
|
98
|
+
"observe",
|
|
99
|
+
]
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""
|
|
2
|
+
memowatch.backends.base
|
|
3
|
+
~~~~~~~~~~~~~~~~~~~~~~~~
|
|
4
|
+
|
|
5
|
+
Abstract base class for all platform backends.
|
|
6
|
+
|
|
7
|
+
A backend is responsible for:
|
|
8
|
+
1. Watching a single directory (optionally recursively).
|
|
9
|
+
2. Emitting raw ``FileSystemEvent`` objects via a callback.
|
|
10
|
+
3. Running in a background thread managed by the ``Observer``.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import abc
|
|
16
|
+
import threading
|
|
17
|
+
from typing import Callable, Optional
|
|
18
|
+
|
|
19
|
+
from memowatch.core.events import FileSystemEvent
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class BaseBackend(abc.ABC):
|
|
23
|
+
"""Abstract base class that every platform backend must implement."""
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
path: str,
|
|
28
|
+
recursive: bool = False,
|
|
29
|
+
callback: Optional[Callable[[FileSystemEvent], None]] = None,
|
|
30
|
+
) -> None:
|
|
31
|
+
self.path = path
|
|
32
|
+
self.recursive = recursive
|
|
33
|
+
self.callback = callback
|
|
34
|
+
self._stop_event = threading.Event()
|
|
35
|
+
self._thread: Optional[threading.Thread] = None
|
|
36
|
+
|
|
37
|
+
# โโ Public API โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
38
|
+
|
|
39
|
+
def start(self) -> None:
|
|
40
|
+
"""Start watching in a background thread."""
|
|
41
|
+
self._stop_event.clear()
|
|
42
|
+
self._thread = threading.Thread(
|
|
43
|
+
target=self._run, daemon=True, name=f"memowatch-{self.name}"
|
|
44
|
+
)
|
|
45
|
+
self._thread.start()
|
|
46
|
+
|
|
47
|
+
def stop(self) -> None:
|
|
48
|
+
"""Signal the backend to stop and wait for its thread to finish."""
|
|
49
|
+
self._stop_event.set()
|
|
50
|
+
if self._thread and self._thread.is_alive():
|
|
51
|
+
self._thread.join(timeout=5.0)
|
|
52
|
+
|
|
53
|
+
def is_alive(self) -> bool:
|
|
54
|
+
return self._thread is not None and self._thread.is_alive()
|
|
55
|
+
|
|
56
|
+
# โโ Emit helper โโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
57
|
+
|
|
58
|
+
def emit(self, event: FileSystemEvent) -> None:
|
|
59
|
+
"""Forward an event to the registered callback."""
|
|
60
|
+
if self.callback is not None:
|
|
61
|
+
self.callback(event)
|
|
62
|
+
|
|
63
|
+
# โโ Abstract interface โโโโโโโโโโโโโโโโโโโโ
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
@abc.abstractmethod
|
|
67
|
+
def name(self) -> str:
|
|
68
|
+
"""Short human-readable name for this backend (e.g. 'win32', 'polling')."""
|
|
69
|
+
|
|
70
|
+
@abc.abstractmethod
|
|
71
|
+
def _run(self) -> None:
|
|
72
|
+
"""Main loop executed in the background thread.
|
|
73
|
+
|
|
74
|
+
Implementations should watch ``self.path``, detect changes, and call
|
|
75
|
+
``self.emit(event)`` for each one. The loop must check
|
|
76
|
+
``self._stop_event.is_set()`` periodically and return when it becomes
|
|
77
|
+
``True``.
|
|
78
|
+
"""
|