Skip to content

Commit

Permalink
Add ignore_permission_denied option (#224)
Browse files Browse the repository at this point in the history
* Support ignoring errors

* Add ignore_permission_denied

* update cli

* Make test specific to Linux

* apply comments

* add environment variable

* empty env var equivilent to unset

* fix test after change

* fix tests and docs

---------

Co-authored-by: Samuel Colvin <[email protected]>
  • Loading branch information
aminalaee and samuelcolvin authored Aug 24, 2023
1 parent ea789d3 commit ea31274
Show file tree
Hide file tree
Showing 10 changed files with 156 additions and 39 deletions.
4 changes: 2 additions & 2 deletions docs/api/rust_backend.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ The rust backend can be accessed directly as follows:
title="Rust backend example"
from watchfiles._rust_notify import RustNotify

r = RustNotify(['first/path', 'second/path'], False, False, 0, True)
r = RustNotify(['first/path', 'second/path'], False, False, 0, True, False)

changes = r.watch(1_600, 50, 100, None)
print(changes)
Expand All @@ -26,7 +26,7 @@ Or using `RustNotify` as a context manager:
title="Rust backend context manager example"
from watchfiles._rust_notify import RustNotify

with RustNotify(['first/path', 'second/path'], False, False, 0, True) as r:
with RustNotify(['first/path', 'second/path'], False, False, 0, True, False) as r:
changes = r.watch(1_600, 50, 100, None)
print(changes)
```
Expand Down
5 changes: 4 additions & 1 deletion docs/cli_help.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ usage: watchfiles [-h] [--ignore-paths [IGNORE_PATHS]]
[--non-recursive] [--verbosity [{warning,info,debug}]]
[--sigint-timeout [SIGINT_TIMEOUT]]
[--grace-period [GRACE_PERIOD]]
[--sigkill-timeout [SIGKILL_TIMEOUT]] [--version]
[--sigkill-timeout [SIGKILL_TIMEOUT]]
[--ignore-permission-denied] [--version]
target [paths ...]

Watch one or more directories and execute either a shell command or a python function on file changes.
Expand Down Expand Up @@ -41,4 +42,6 @@ options:
Number of seconds after the process is started before watching for changes.
--sigkill-timeout [SIGKILL_TIMEOUT]
How long to wait for the sigkill timeout before issuing a timeout exception.
--ignore-permission-denied
Ignore permission denied errors while watching files and directories.
--version, -V show program's version number and exit
18 changes: 14 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,23 @@ fn map_watch_error(error: notify::Error) -> PyErr {

// macro to avoid duplicated code below
macro_rules! watcher_paths {
($watcher:ident, $paths:ident, $debug:ident, $recursive:ident) => {
($watcher:ident, $paths:ident, $debug:ident, $recursive:ident, $ignore_permission_denied:ident) => {
let mode = if $recursive {
RecursiveMode::Recursive
} else {
RecursiveMode::NonRecursive
};
for watch_path in $paths.into_iter() {
$watcher.watch(Path::new(&watch_path), mode).map_err(map_watch_error)?;
let result = $watcher.watch(Path::new(&watch_path), mode);
match result {
Err(err) => {
let err = map_watch_error(err);
if !$ignore_permission_denied {
return Err(err);
}
}
_ => (),
}
}
if $debug {
eprintln!("watcher: {:?}", $watcher);
Expand All @@ -101,6 +110,7 @@ impl RustNotify {
force_polling: bool,
poll_delay_ms: u64,
recursive: bool,
ignore_permission_denied: bool,
) -> PyResult<Self> {
let changes: Arc<Mutex<HashSet<(u8, String)>>> = Arc::new(Mutex::new(HashSet::<(u8, String)>::new()));
let error: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
Expand Down Expand Up @@ -184,7 +194,7 @@ impl RustNotify {
Ok(watcher) => watcher,
Err(e) => return wf_error!($msg_template, e),
};
watcher_paths!(watcher, watch_paths, debug, recursive);
watcher_paths!(watcher, watch_paths, debug, recursive, ignore_permission_denied);
Ok(WatcherEnum::Poll(watcher))
}};
}
Expand All @@ -195,7 +205,7 @@ impl RustNotify {
match RecommendedWatcher::new(event_handler.clone(), NotifyConfig::default()) {
Ok(watcher) => {
let mut watcher = watcher;
watcher_paths!(watcher, watch_paths, debug, recursive);
watcher_paths!(watcher, watch_paths, debug, recursive, ignore_permission_denied);
Ok(WatcherEnum::Recommended(watcher))
}
Err(error) => {
Expand Down
30 changes: 30 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ def test_function(mocker, tmp_path):
sigint_timeout=5,
sigkill_timeout=1,
recursive=True,
ignore_permission_denied=False,
)


Expand Down Expand Up @@ -54,6 +55,7 @@ def test_ignore_paths(mocker, tmp_work_path):
sigint_timeout=5,
sigkill_timeout=1,
recursive=True,
ignore_permission_denied=False,
)


Expand Down Expand Up @@ -108,6 +110,7 @@ def test_command(mocker, tmp_work_path):
sigint_timeout=5,
sigkill_timeout=1,
recursive=True,
ignore_permission_denied=False,
)


Expand All @@ -126,6 +129,7 @@ def test_verbosity(mocker, tmp_path):
sigint_timeout=5,
sigkill_timeout=1,
recursive=True,
ignore_permission_denied=False,
)


Expand All @@ -144,6 +148,7 @@ def test_verbose(mocker, tmp_path):
sigint_timeout=5,
sigkill_timeout=1,
recursive=True,
ignore_permission_denied=False,
)


Expand All @@ -162,6 +167,7 @@ def test_non_recursive(mocker, tmp_path):
sigint_timeout=5,
sigkill_timeout=1,
recursive=False,
ignore_permission_denied=False,
)


Expand All @@ -180,6 +186,7 @@ def test_filter_all(mocker, tmp_path, capsys):
sigint_timeout=5,
sigkill_timeout=1,
recursive=True,
ignore_permission_denied=False,
)
out, err = capsys.readouterr()
assert out == ''
Expand All @@ -201,6 +208,7 @@ def test_filter_default(mocker, tmp_path):
sigint_timeout=5,
sigkill_timeout=1,
recursive=True,
ignore_permission_denied=False,
)


Expand All @@ -219,6 +227,7 @@ def test_set_type(mocker, tmp_path):
sigint_timeout=5,
sigkill_timeout=1,
recursive=True,
ignore_permission_denied=False,
)


Expand Down Expand Up @@ -275,6 +284,7 @@ def test_args(mocker, tmp_path, reset_argv, caplog):
sigint_timeout=5,
sigkill_timeout=1,
recursive=True,
ignore_permission_denied=False,
)
assert sys.argv == ['os.getcwd', '--version']
assert 'WARNING: --args' not in caplog.text
Expand All @@ -297,5 +307,25 @@ def test_args_command(mocker, tmp_path, caplog):
sigint_timeout=5,
sigkill_timeout=1,
recursive=True,
ignore_permission_denied=False,
)
assert 'WARNING: --args is only used when the target is a function\n' in caplog.text


def test_ignore_permission_denied(mocker, tmp_path):
mocker.patch('watchfiles.cli.sys.stdin.fileno')
mocker.patch('os.ttyname', return_value='/path/to/tty')
mock_run_process = mocker.patch('watchfiles.cli.run_process')
cli('--ignore-permission-denied', 'os.getcwd', str(tmp_path))
mock_run_process.assert_called_once_with(
tmp_path,
target='os.getcwd',
target_type='function',
watch_filter=IsInstance(DefaultFilter, only_direct_instance=True),
debug=False,
grace_period=0,
sigint_timeout=5,
sigkill_timeout=1,
recursive=True,
ignore_permission_denied=True,
)
4 changes: 2 additions & 2 deletions tests/test_force_polling.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def test_watch_polling_not_env(mocker):
for _ in watch('.'):
pass

m.assert_called_once_with(['.'], False, False, 300, True)
m.assert_called_once_with(['.'], False, False, 300, True, False)


def test_watch_polling_env(mocker, env: SetEnv):
Expand All @@ -39,7 +39,7 @@ def test_watch_polling_env(mocker, env: SetEnv):
for _ in watch('.'):
pass

m.assert_called_once_with(['.'], False, True, 300, True)
m.assert_called_once_with(['.'], False, True, 300, True, False)


@pytest.mark.parametrize(
Expand Down
Loading

0 comments on commit ea31274

Please sign in to comment.