watchfiles
watchfiles copied to clipboard
`FileNotFoundError` during development
Description
Hi Samuel :wave:
I'm having a bad experience when developing on uvicorn
, and I'd really appreciate some help. :pray:
Since a couple of days ago I've been facing a lot of FileNotFoundError
, which happens from time to time.
Would you perhaps be able to help me here?
Full Pytest Trace
uvicorn on master [$?] via 🐍 v3.10.5 (uvicorn3.10) on ☁️ [email protected] took 51s
❯ coverage run -m pytest
============================================================================ test session starts ============================================================================
platform linux -- Python 3.10.5, pytest-7.1.3, pluggy-1.0.0
rootdir: /home/marcelo/Development/encode/uvicorn, configfile: setup.cfg
plugins: anyio-3.6.1, mock-3.10.0, asyncio-0.15.1, time-machine-2.8.1
collected 448 items
tests/test_auto_detection.py ... [ 0%]
tests/test_cli.py ............ [ 3%]
tests/test_config.py .................................................. [ 14%]
tests/test_default_headers.py ...... [ 15%]
tests/test_lifespan.py ............... [ 19%]
tests/test_main.py ......... [ 21%]
tests/test_ssl.py .... [ 22%]
tests/test_subprocess.py .. [ 22%]
tests/importer/test_importer.py ...... [ 23%]
tests/middleware/test_logging.py .............. [ 27%]
tests/middleware/test_message_logger.py .. [ 27%]
tests/middleware/test_proxy_headers.py ........... [ 29%]
tests/middleware/test_wsgi.py ...... [ 31%]
tests/protocols/test_http.py ........................................................................................................ [ 54%]
tests/protocols/test_utils.py ...... [ 55%]
tests/protocols/test_websocket.py ................................................................................................................................... [ 85%]
............................... [ 91%]
tests/supervisors/test_multiprocess.py . [ 92%]
tests/supervisors/test_reload.py .....F..FF..FF...F..F..FF.......... [100%]
================================================================================= FAILURES ==================================================================================
_________________________________________________ TestBaseReload.test_reload_when_python_file_is_changed[WatchFilesReload] __________________________________________________
self = <test_reload.TestBaseReload object at 0x7f759e5d6830>, touch_soon = <function touch_soon.<locals>.start at 0x7f759b2d05e0>
@pytest.mark.parametrize(
"reloader_class", [StatReload, WatchGodReload, WatchFilesReload]
)
def test_reload_when_python_file_is_changed(self, touch_soon) -> None:
file = self.reload_path / "main.py"
with as_cwd(self.reload_path):
config = Config(app="tests.test_config:asgi_app", reload=True)
reloader = self._setup_reloader(config)
> changes = self._reload_tester(touch_soon, reloader, file)
tests/supervisors/test_reload.py:91:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
tests/supervisors/test_reload.py:64: in _reload_tester
return next(reloader)
uvicorn/supervisors/basereload.py:64: in __next__
return self.should_restart()
uvicorn/supervisors/watchfilesreload.py:85: in should_restart
changes = next(self.watcher)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
watch_filter = None, debounce = 1600, step = 50, stop_event = <threading.Event object at 0x7f759dde3190>
def watch(
*paths: Union[Path, str],
watch_filter: Optional[Callable[['Change', str], bool]] = DefaultFilter(),
debounce: int = 1_600,
step: int = 50,
stop_event: Optional['AbstractEvent'] = None,
rust_timeout: int = 5_000,
yield_on_timeout: bool = False,
debug: bool = False,
raise_interrupt: bool = True,
force_polling: Optional[bool] = None,
poll_delay_ms: int = 300,
recursive: bool = True,
) -> Generator[Set[FileChange], None, None]:
"""
Watch one or more paths and yield a set of changes whenever files change.
The paths watched can be directories or files, directories are watched recursively - changes in subdirectories
are also detected.
#### Force polling
Notify will fall back to file polling if it can't use file system notifications, but we also force notify
to us polling if the `force_polling` argument is `True`; if `force_polling` is unset (or `None`), we enable
force polling thus:
* if the `WATCHFILES_FORCE_POLLING` environment variable exists and is not empty:
* if the value is `false`, `disable` or `disabled`, force polling is disabled
* otherwise, force polling is enabled
* otherwise, we enable force polling only if we detect we're running on WSL (Windows Subsystem for Linux)
Args:
*paths: filesystem paths to watch.
watch_filter: callable used to filter out changes which are not important, you can either use a raw callable
or a [`BaseFilter`][watchfiles.BaseFilter] instance,
defaults to an instance of [`DefaultFilter`][watchfiles.DefaultFilter]. To keep all changes, use `None`.
debounce: maximum time in milliseconds to group changes over before yielding them.
step: time to wait for new changes in milliseconds, if no changes are detected in this time, and
at least one change has been detected, the changes are yielded.
stop_event: event to stop watching, if this is set, the generator will stop iteration,
this can be anything with an `is_set()` method which returns a bool, e.g. `threading.Event()`.
rust_timeout: maximum time in milliseconds to wait in the rust code for changes, `0` means no timeout.
yield_on_timeout: if `True`, the generator will yield upon timeout in rust even if no changes are detected.
debug: whether to print information about all filesystem changes in rust to stdout.
raise_interrupt: whether to re-raise `KeyboardInterrupt`s, or suppress the error and just stop iterating.
force_polling: See [Force polling](#force-polling) above.
poll_delay_ms: delay between polling for changes, only used if `force_polling=True`.
recursive: if `True`, watch for changes in sub-directories recursively, otherwise watch only for changes in the
top-level directory, default is `True`.
Yields:
The generator yields sets of [`FileChange`][watchfiles.main.FileChange]s.
```py title="Example of watch usage"
from watchfiles import watch
for changes in watch('./first/dir', './second/dir', raise_interrupt=False):
print(changes)
```
"""
force_polling = _default_force_polling(force_polling)
> with RustNotify([str(p) for p in paths], debug, force_polling, poll_delay_ms, recursive) as watcher:
E FileNotFoundError: OS file watch limit reached. about ["/tmp/pytest-of-marcelo/pytest-1/reload_directory1/app/css"]
../../../.pyenv/versions/uvicorn3.10/lib/python3.10/site-packages/watchfiles/main.py:119: FileNotFoundError
--------------------------------------------------------------------------- Captured stderr call ----------------------------------------------------------------------------
INFO: Will watch for changes in these directories: ['/tmp/pytest-of-marcelo/pytest-1/reload_directory1']
INFO: Started reloader process [28645] using WatchFiles
----------------------------------------------------------------------------- Captured log call -----------------------------------------------------------------------------
INFO uvicorn.error:config.py:353 Will watch for changes in these directories: ['/tmp/pytest-of-marcelo/pytest-1/reload_directory1']
INFO uvicorn.error:basereload.py:72 Started reloader process [28645] using WatchFiles
_________________________________________ TestBaseReload.test_should_reload_when_python_file_in_subdir_is_changed[WatchFilesReload] _________________________________________
self = <test_reload.TestBaseReload object at 0x7f759e5d58d0>, touch_soon = <function touch_soon.<locals>.start at 0x7f759b2ffa30>
@pytest.mark.parametrize(
"reloader_class", [StatReload, WatchGodReload, WatchFilesReload]
)
def test_should_reload_when_python_file_in_subdir_is_changed(
self, touch_soon
) -> None:
file = self.reload_path / "app" / "sub" / "sub.py"
with as_cwd(self.reload_path):
config = Config(app="tests.test_config:asgi_app", reload=True)
reloader = self._setup_reloader(config)
> assert self._reload_tester(touch_soon, reloader, file)
tests/supervisors/test_reload.py:108:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
tests/supervisors/test_reload.py:64: in _reload_tester
return next(reloader)
uvicorn/supervisors/basereload.py:64: in __next__
return self.should_restart()
uvicorn/supervisors/watchfilesreload.py:85: in should_restart
changes = next(self.watcher)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
watch_filter = None, debounce = 1600, step = 50, stop_event = <threading.Event object at 0x7f759dde1030>
def watch(
*paths: Union[Path, str],
watch_filter: Optional[Callable[['Change', str], bool]] = DefaultFilter(),
debounce: int = 1_600,
step: int = 50,
stop_event: Optional['AbstractEvent'] = None,
rust_timeout: int = 5_000,
yield_on_timeout: bool = False,
debug: bool = False,
raise_interrupt: bool = True,
force_polling: Optional[bool] = None,
poll_delay_ms: int = 300,
recursive: bool = True,
) -> Generator[Set[FileChange], None, None]:
"""
Watch one or more paths and yield a set of changes whenever files change.
The paths watched can be directories or files, directories are watched recursively - changes in subdirectories
are also detected.
#### Force polling
Notify will fall back to file polling if it can't use file system notifications, but we also force notify
to us polling if the `force_polling` argument is `True`; if `force_polling` is unset (or `None`), we enable
force polling thus:
* if the `WATCHFILES_FORCE_POLLING` environment variable exists and is not empty:
* if the value is `false`, `disable` or `disabled`, force polling is disabled
* otherwise, force polling is enabled
* otherwise, we enable force polling only if we detect we're running on WSL (Windows Subsystem for Linux)
Args:
*paths: filesystem paths to watch.
watch_filter: callable used to filter out changes which are not important, you can either use a raw callable
or a [`BaseFilter`][watchfiles.BaseFilter] instance,
defaults to an instance of [`DefaultFilter`][watchfiles.DefaultFilter]. To keep all changes, use `None`.
debounce: maximum time in milliseconds to group changes over before yielding them.
step: time to wait for new changes in milliseconds, if no changes are detected in this time, and
at least one change has been detected, the changes are yielded.
stop_event: event to stop watching, if this is set, the generator will stop iteration,
this can be anything with an `is_set()` method which returns a bool, e.g. `threading.Event()`.
rust_timeout: maximum time in milliseconds to wait in the rust code for changes, `0` means no timeout.
yield_on_timeout: if `True`, the generator will yield upon timeout in rust even if no changes are detected.
debug: whether to print information about all filesystem changes in rust to stdout.
raise_interrupt: whether to re-raise `KeyboardInterrupt`s, or suppress the error and just stop iterating.
force_polling: See [Force polling](#force-polling) above.
poll_delay_ms: delay between polling for changes, only used if `force_polling=True`.
recursive: if `True`, watch for changes in sub-directories recursively, otherwise watch only for changes in the
top-level directory, default is `True`.
Yields:
The generator yields sets of [`FileChange`][watchfiles.main.FileChange]s.
```py title="Example of watch usage"
from watchfiles import watch
for changes in watch('./first/dir', './second/dir', raise_interrupt=False):
print(changes)
```
"""
force_polling = _default_force_polling(force_polling)
> with RustNotify([str(p) for p in paths], debug, force_polling, poll_delay_ms, recursive) as watcher:
E FileNotFoundError: OS file watch limit reached. about ["/tmp/pytest-of-marcelo/pytest-1/reload_directory1/app/css"]
../../../.pyenv/versions/uvicorn3.10/lib/python3.10/site-packages/watchfiles/main.py:119: FileNotFoundError
--------------------------------------------------------------------------- Captured stderr call ----------------------------------------------------------------------------
INFO: Will watch for changes in these directories: ['/tmp/pytest-of-marcelo/pytest-1/reload_directory1']
INFO: Started reloader process [28645] using WatchFiles
----------------------------------------------------------------------------- Captured log call -----------------------------------------------------------------------------
INFO uvicorn.error:config.py:353 Will watch for changes in these directories: ['/tmp/pytest-of-marcelo/pytest-1/reload_directory1']
INFO uvicorn.error:basereload.py:72 Started reloader process [28645] using WatchFiles
__________________________________ TestBaseReload.test_should_not_reload_when_python_file_in_excluded_subdir_is_changed[WatchFilesReload] ___________________________________
self = <test_reload.TestBaseReload object at 0x7f759e5d6a40>, touch_soon = <function touch_soon.<locals>.start at 0x7f759b2ff130>
@pytest.mark.parametrize("reloader_class", [WatchFilesReload, WatchGodReload])
def test_should_not_reload_when_python_file_in_excluded_subdir_is_changed(
self, touch_soon
) -> None:
sub_dir = self.reload_path / "app" / "sub"
sub_file = sub_dir / "sub.py"
with as_cwd(self.reload_path):
config = Config(
app="tests.test_config:asgi_app",
reload=True,
reload_excludes=[str(sub_dir)],
)
reloader = self._setup_reloader(config)
> assert not self._reload_tester(touch_soon, reloader, sub_file)
tests/supervisors/test_reload.py:127:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
tests/supervisors/test_reload.py:64: in _reload_tester
return next(reloader)
uvicorn/supervisors/basereload.py:64: in __next__
return self.should_restart()
uvicorn/supervisors/watchfilesreload.py:85: in should_restart
changes = next(self.watcher)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
watch_filter = None, debounce = 1600, step = 50, stop_event = <threading.Event object at 0x7f759dde3f10>
def watch(
*paths: Union[Path, str],
watch_filter: Optional[Callable[['Change', str], bool]] = DefaultFilter(),
debounce: int = 1_600,
step: int = 50,
stop_event: Optional['AbstractEvent'] = None,
rust_timeout: int = 5_000,
yield_on_timeout: bool = False,
debug: bool = False,
raise_interrupt: bool = True,
force_polling: Optional[bool] = None,
poll_delay_ms: int = 300,
recursive: bool = True,
) -> Generator[Set[FileChange], None, None]:
"""
Watch one or more paths and yield a set of changes whenever files change.
The paths watched can be directories or files, directories are watched recursively - changes in subdirectories
are also detected.
#### Force polling
Notify will fall back to file polling if it can't use file system notifications, but we also force notify
to us polling if the `force_polling` argument is `True`; if `force_polling` is unset (or `None`), we enable
force polling thus:
* if the `WATCHFILES_FORCE_POLLING` environment variable exists and is not empty:
* if the value is `false`, `disable` or `disabled`, force polling is disabled
* otherwise, force polling is enabled
* otherwise, we enable force polling only if we detect we're running on WSL (Windows Subsystem for Linux)
Args:
*paths: filesystem paths to watch.
watch_filter: callable used to filter out changes which are not important, you can either use a raw callable
or a [`BaseFilter`][watchfiles.BaseFilter] instance,
defaults to an instance of [`DefaultFilter`][watchfiles.DefaultFilter]. To keep all changes, use `None`.
debounce: maximum time in milliseconds to group changes over before yielding them.
step: time to wait for new changes in milliseconds, if no changes are detected in this time, and
at least one change has been detected, the changes are yielded.
stop_event: event to stop watching, if this is set, the generator will stop iteration,
this can be anything with an `is_set()` method which returns a bool, e.g. `threading.Event()`.
rust_timeout: maximum time in milliseconds to wait in the rust code for changes, `0` means no timeout.
yield_on_timeout: if `True`, the generator will yield upon timeout in rust even if no changes are detected.
debug: whether to print information about all filesystem changes in rust to stdout.
raise_interrupt: whether to re-raise `KeyboardInterrupt`s, or suppress the error and just stop iterating.
force_polling: See [Force polling](#force-polling) above.
poll_delay_ms: delay between polling for changes, only used if `force_polling=True`.
recursive: if `True`, watch for changes in sub-directories recursively, otherwise watch only for changes in the
top-level directory, default is `True`.
Yields:
The generator yields sets of [`FileChange`][watchfiles.main.FileChange]s.
```py title="Example of watch usage"
from watchfiles import watch
for changes in watch('./first/dir', './second/dir', raise_interrupt=False):
print(changes)
```
"""
force_polling = _default_force_polling(force_polling)
> with RustNotify([str(p) for p in paths], debug, force_polling, poll_delay_ms, recursive) as watcher:
E FileNotFoundError: OS file watch limit reached. about ["/tmp/pytest-of-marcelo/pytest-1/reload_directory1/app/css"]
../../../.pyenv/versions/uvicorn3.10/lib/python3.10/site-packages/watchfiles/main.py:119: FileNotFoundError
--------------------------------------------------------------------------- Captured stderr call ----------------------------------------------------------------------------
INFO: Will watch for changes in these directories: ['/tmp/pytest-of-marcelo/pytest-1/reload_directory1']
INFO: Started reloader process [28645] using WatchFiles
----------------------------------------------------------------------------- Captured log call -----------------------------------------------------------------------------
INFO uvicorn.error:config.py:353 Will watch for changes in these directories: ['/tmp/pytest-of-marcelo/pytest-1/reload_directory1']
INFO uvicorn.error:basereload.py:72 Started reloader process [28645] using WatchFiles
__________________________________________ TestBaseReload.test_reload_when_pattern_matched_file_is_changed[WatchFilesReload-True] ___________________________________________
self = <test_reload.TestBaseReload object at 0x7f759e5d6dd0>, result = True, touch_soon = <function touch_soon.<locals>.start at 0x7f759a1a0670>
@pytest.mark.parametrize(
"reloader_class, result", [(StatReload, False), (WatchFilesReload, True)]
)
def test_reload_when_pattern_matched_file_is_changed(
self, result: bool, touch_soon
) -> None:
file = self.reload_path / "app" / "js" / "main.js"
with as_cwd(self.reload_path):
config = Config(
app="tests.test_config:asgi_app", reload=True, reload_includes=["*.js"]
)
reloader = self._setup_reloader(config)
> assert bool(self._reload_tester(touch_soon, reloader, file)) == result
tests/supervisors/test_reload.py:145:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
tests/supervisors/test_reload.py:64: in _reload_tester
return next(reloader)
uvicorn/supervisors/basereload.py:64: in __next__
return self.should_restart()
uvicorn/supervisors/watchfilesreload.py:85: in should_restart
changes = next(self.watcher)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
watch_filter = None, debounce = 1600, step = 50, stop_event = <threading.Event object at 0x7f759dde69e0>
def watch(
*paths: Union[Path, str],
watch_filter: Optional[Callable[['Change', str], bool]] = DefaultFilter(),
debounce: int = 1_600,
step: int = 50,
stop_event: Optional['AbstractEvent'] = None,
rust_timeout: int = 5_000,
yield_on_timeout: bool = False,
debug: bool = False,
raise_interrupt: bool = True,
force_polling: Optional[bool] = None,
poll_delay_ms: int = 300,
recursive: bool = True,
) -> Generator[Set[FileChange], None, None]:
"""
Watch one or more paths and yield a set of changes whenever files change.
The paths watched can be directories or files, directories are watched recursively - changes in subdirectories
are also detected.
#### Force polling
Notify will fall back to file polling if it can't use file system notifications, but we also force notify
to us polling if the `force_polling` argument is `True`; if `force_polling` is unset (or `None`), we enable
force polling thus:
* if the `WATCHFILES_FORCE_POLLING` environment variable exists and is not empty:
* if the value is `false`, `disable` or `disabled`, force polling is disabled
* otherwise, force polling is enabled
* otherwise, we enable force polling only if we detect we're running on WSL (Windows Subsystem for Linux)
Args:
*paths: filesystem paths to watch.
watch_filter: callable used to filter out changes which are not important, you can either use a raw callable
or a [`BaseFilter`][watchfiles.BaseFilter] instance,
defaults to an instance of [`DefaultFilter`][watchfiles.DefaultFilter]. To keep all changes, use `None`.
debounce: maximum time in milliseconds to group changes over before yielding them.
step: time to wait for new changes in milliseconds, if no changes are detected in this time, and
at least one change has been detected, the changes are yielded.
stop_event: event to stop watching, if this is set, the generator will stop iteration,
this can be anything with an `is_set()` method which returns a bool, e.g. `threading.Event()`.
rust_timeout: maximum time in milliseconds to wait in the rust code for changes, `0` means no timeout.
yield_on_timeout: if `True`, the generator will yield upon timeout in rust even if no changes are detected.
debug: whether to print information about all filesystem changes in rust to stdout.
raise_interrupt: whether to re-raise `KeyboardInterrupt`s, or suppress the error and just stop iterating.
force_polling: See [Force polling](#force-polling) above.
poll_delay_ms: delay between polling for changes, only used if `force_polling=True`.
recursive: if `True`, watch for changes in sub-directories recursively, otherwise watch only for changes in the
top-level directory, default is `True`.
Yields:
The generator yields sets of [`FileChange`][watchfiles.main.FileChange]s.
```py title="Example of watch usage"
from watchfiles import watch
for changes in watch('./first/dir', './second/dir', raise_interrupt=False):
print(changes)
```
"""
force_polling = _default_force_polling(force_polling)
> with RustNotify([str(p) for p in paths], debug, force_polling, poll_delay_ms, recursive) as watcher:
E FileNotFoundError: OS file watch limit reached. about ["/tmp/pytest-of-marcelo/pytest-1/reload_directory1/app/css"]
../../../.pyenv/versions/uvicorn3.10/lib/python3.10/site-packages/watchfiles/main.py:119: FileNotFoundError
--------------------------------------------------------------------------- Captured stderr call ----------------------------------------------------------------------------
INFO: Will watch for changes in these directories: ['/tmp/pytest-of-marcelo/pytest-1/reload_directory1']
INFO: Started reloader process [28645] using WatchFiles
----------------------------------------------------------------------------- Captured log call -----------------------------------------------------------------------------
INFO uvicorn.error:config.py:353 Will watch for changes in these directories: ['/tmp/pytest-of-marcelo/pytest-1/reload_directory1']
INFO uvicorn.error:basereload.py:72 Started reloader process [28645] using WatchFiles
____________________________________ TestBaseReload.test_should_not_reload_when_exclude_pattern_match_file_is_changed[WatchFilesReload] _____________________________________
self = <test_reload.TestBaseReload object at 0x7f759e5d6fe0>, touch_soon = <function touch_soon.<locals>.start at 0x7f759a1a0e50>
@pytest.mark.parametrize("reloader_class", [WatchFilesReload, WatchGodReload])
def test_should_not_reload_when_exclude_pattern_match_file_is_changed(
self, touch_soon
) -> None:
python_file = self.reload_path / "app" / "src" / "main.py"
css_file = self.reload_path / "app" / "css" / "main.css"
js_file = self.reload_path / "app" / "js" / "main.js"
with as_cwd(self.reload_path):
config = Config(
app="tests.test_config:asgi_app",
reload=True,
reload_includes=["*"],
reload_excludes=["*.js"],
)
reloader = self._setup_reloader(config)
> assert self._reload_tester(touch_soon, reloader, python_file)
tests/supervisors/test_reload.py:166:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
tests/supervisors/test_reload.py:64: in _reload_tester
return next(reloader)
uvicorn/supervisors/basereload.py:64: in __next__
return self.should_restart()
uvicorn/supervisors/watchfilesreload.py:85: in should_restart
changes = next(self.watcher)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
watch_filter = None, debounce = 1600, step = 50, stop_event = <threading.Event object at 0x7f759a17f430>
def watch(
*paths: Union[Path, str],
watch_filter: Optional[Callable[['Change', str], bool]] = DefaultFilter(),
debounce: int = 1_600,
step: int = 50,
stop_event: Optional['AbstractEvent'] = None,
rust_timeout: int = 5_000,
yield_on_timeout: bool = False,
debug: bool = False,
raise_interrupt: bool = True,
force_polling: Optional[bool] = None,
poll_delay_ms: int = 300,
recursive: bool = True,
) -> Generator[Set[FileChange], None, None]:
"""
Watch one or more paths and yield a set of changes whenever files change.
The paths watched can be directories or files, directories are watched recursively - changes in subdirectories
are also detected.
#### Force polling
Notify will fall back to file polling if it can't use file system notifications, but we also force notify
to us polling if the `force_polling` argument is `True`; if `force_polling` is unset (or `None`), we enable
force polling thus:
* if the `WATCHFILES_FORCE_POLLING` environment variable exists and is not empty:
* if the value is `false`, `disable` or `disabled`, force polling is disabled
* otherwise, force polling is enabled
* otherwise, we enable force polling only if we detect we're running on WSL (Windows Subsystem for Linux)
Args:
*paths: filesystem paths to watch.
watch_filter: callable used to filter out changes which are not important, you can either use a raw callable
or a [`BaseFilter`][watchfiles.BaseFilter] instance,
defaults to an instance of [`DefaultFilter`][watchfiles.DefaultFilter]. To keep all changes, use `None`.
debounce: maximum time in milliseconds to group changes over before yielding them.
step: time to wait for new changes in milliseconds, if no changes are detected in this time, and
at least one change has been detected, the changes are yielded.
stop_event: event to stop watching, if this is set, the generator will stop iteration,
this can be anything with an `is_set()` method which returns a bool, e.g. `threading.Event()`.
rust_timeout: maximum time in milliseconds to wait in the rust code for changes, `0` means no timeout.
yield_on_timeout: if `True`, the generator will yield upon timeout in rust even if no changes are detected.
debug: whether to print information about all filesystem changes in rust to stdout.
raise_interrupt: whether to re-raise `KeyboardInterrupt`s, or suppress the error and just stop iterating.
force_polling: See [Force polling](#force-polling) above.
poll_delay_ms: delay between polling for changes, only used if `force_polling=True`.
recursive: if `True`, watch for changes in sub-directories recursively, otherwise watch only for changes in the
top-level directory, default is `True`.
Yields:
The generator yields sets of [`FileChange`][watchfiles.main.FileChange]s.
```py title="Example of watch usage"
from watchfiles import watch
for changes in watch('./first/dir', './second/dir', raise_interrupt=False):
print(changes)
```
"""
force_polling = _default_force_polling(force_polling)
> with RustNotify([str(p) for p in paths], debug, force_polling, poll_delay_ms, recursive) as watcher:
E FileNotFoundError: OS file watch limit reached. about ["/tmp/pytest-of-marcelo/pytest-1/reload_directory1/app/css"]
../../../.pyenv/versions/uvicorn3.10/lib/python3.10/site-packages/watchfiles/main.py:119: FileNotFoundError
--------------------------------------------------------------------------- Captured stderr call ----------------------------------------------------------------------------
INFO: Will watch for changes in these directories: ['/tmp/pytest-of-marcelo/pytest-1/reload_directory1/.dotted_dir', '/tmp/pytest-of-marcelo/pytest-1/reload_directory1/app', '/tmp/pytest-of-marcelo/pytest-1/reload_directory1/app_first', '/tmp/pytest-of-marcelo/pytest-1/reload_directory1/app_second', '/tmp/pytest-of-marcelo/pytest-1/reload_directory1/app_third', '/tmp/pytest-of-marcelo/pytest-1/reload_directory1/ext']
INFO: Started reloader process [28645] using WatchFiles
----------------------------------------------------------------------------- Captured log call -----------------------------------------------------------------------------
INFO uvicorn.error:config.py:353 Will watch for changes in these directories: ['/tmp/pytest-of-marcelo/pytest-1/reload_directory1/.dotted_dir', '/tmp/pytest-of-marcelo/pytest-1/reload_directory1/app', '/tmp/pytest-of-marcelo/pytest-1/reload_directory1/app_first', '/tmp/pytest-of-marcelo/pytest-1/reload_directory1/app_second', '/tmp/pytest-of-marcelo/pytest-1/reload_directory1/app_third', '/tmp/pytest-of-marcelo/pytest-1/reload_directory1/ext']
INFO uvicorn.error:basereload.py:72 Started reloader process [28645] using WatchFiles
_____________________________________________ TestBaseReload.test_should_not_reload_when_dot_file_is_changed[WatchFilesReload] ______________________________________________
self = <test_reload.TestBaseReload object at 0x7f759e5d7430>, touch_soon = <function touch_soon.<locals>.start at 0x7f759b354310>
@pytest.mark.parametrize(
"reloader_class", [StatReload, WatchGodReload, WatchFilesReload]
)
def test_should_not_reload_when_dot_file_is_changed(self, touch_soon) -> None:
file = self.reload_path / ".dotted"
with as_cwd(self.reload_path):
config = Config(app="tests.test_config:asgi_app", reload=True)
reloader = self._setup_reloader(config)
> assert not self._reload_tester(touch_soon, reloader, file)
tests/supervisors/test_reload.py:182:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
tests/supervisors/test_reload.py:64: in _reload_tester
return next(reloader)
uvicorn/supervisors/basereload.py:64: in __next__
return self.should_restart()
uvicorn/supervisors/watchfilesreload.py:85: in should_restart
changes = next(self.watcher)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
watch_filter = None, debounce = 1600, step = 50, stop_event = <threading.Event object at 0x7f759de1d990>
def watch(
*paths: Union[Path, str],
watch_filter: Optional[Callable[['Change', str], bool]] = DefaultFilter(),
debounce: int = 1_600,
step: int = 50,
stop_event: Optional['AbstractEvent'] = None,
rust_timeout: int = 5_000,
yield_on_timeout: bool = False,
debug: bool = False,
raise_interrupt: bool = True,
force_polling: Optional[bool] = None,
poll_delay_ms: int = 300,
recursive: bool = True,
) -> Generator[Set[FileChange], None, None]:
"""
Watch one or more paths and yield a set of changes whenever files change.
The paths watched can be directories or files, directories are watched recursively - changes in subdirectories
are also detected.
#### Force polling
Notify will fall back to file polling if it can't use file system notifications, but we also force notify
to us polling if the `force_polling` argument is `True`; if `force_polling` is unset (or `None`), we enable
force polling thus:
* if the `WATCHFILES_FORCE_POLLING` environment variable exists and is not empty:
* if the value is `false`, `disable` or `disabled`, force polling is disabled
* otherwise, force polling is enabled
* otherwise, we enable force polling only if we detect we're running on WSL (Windows Subsystem for Linux)
Args:
*paths: filesystem paths to watch.
watch_filter: callable used to filter out changes which are not important, you can either use a raw callable
or a [`BaseFilter`][watchfiles.BaseFilter] instance,
defaults to an instance of [`DefaultFilter`][watchfiles.DefaultFilter]. To keep all changes, use `None`.
debounce: maximum time in milliseconds to group changes over before yielding them.
step: time to wait for new changes in milliseconds, if no changes are detected in this time, and
at least one change has been detected, the changes are yielded.
stop_event: event to stop watching, if this is set, the generator will stop iteration,
this can be anything with an `is_set()` method which returns a bool, e.g. `threading.Event()`.
rust_timeout: maximum time in milliseconds to wait in the rust code for changes, `0` means no timeout.
yield_on_timeout: if `True`, the generator will yield upon timeout in rust even if no changes are detected.
debug: whether to print information about all filesystem changes in rust to stdout.
raise_interrupt: whether to re-raise `KeyboardInterrupt`s, or suppress the error and just stop iterating.
force_polling: See [Force polling](#force-polling) above.
poll_delay_ms: delay between polling for changes, only used if `force_polling=True`.
recursive: if `True`, watch for changes in sub-directories recursively, otherwise watch only for changes in the
top-level directory, default is `True`.
Yields:
The generator yields sets of [`FileChange`][watchfiles.main.FileChange]s.
```py title="Example of watch usage"
from watchfiles import watch
for changes in watch('./first/dir', './second/dir', raise_interrupt=False):
print(changes)
```
"""
force_polling = _default_force_polling(force_polling)
> with RustNotify([str(p) for p in paths], debug, force_polling, poll_delay_ms, recursive) as watcher:
E FileNotFoundError: OS file watch limit reached. about ["/tmp/pytest-of-marcelo/pytest-1/reload_directory1/app/css"]
../../../.pyenv/versions/uvicorn3.10/lib/python3.10/site-packages/watchfiles/main.py:119: FileNotFoundError
--------------------------------------------------------------------------- Captured stderr call ----------------------------------------------------------------------------
INFO: Will watch for changes in these directories: ['/tmp/pytest-of-marcelo/pytest-1/reload_directory1']
INFO: Started reloader process [28645] using WatchFiles
----------------------------------------------------------------------------- Captured log call -----------------------------------------------------------------------------
INFO uvicorn.error:config.py:353 Will watch for changes in these directories: ['/tmp/pytest-of-marcelo/pytest-1/reload_directory1']
INFO uvicorn.error:basereload.py:72 Started reloader process [28645] using WatchFiles
___________________________________________ TestBaseReload.test_should_reload_when_directories_have_same_prefix[WatchFilesReload] ___________________________________________
self = <test_reload.TestBaseReload object at 0x7f759e5d7790>, touch_soon = <function touch_soon.<locals>.start at 0x7f759dcfd090>
@pytest.mark.parametrize(
"reloader_class", [StatReload, WatchGodReload, WatchFilesReload]
)
def test_should_reload_when_directories_have_same_prefix(self, touch_soon) -> None:
app_dir = self.reload_path / "app"
app_file = app_dir / "src" / "main.py"
app_first_dir = self.reload_path / "app_first"
app_first_file = app_first_dir / "src" / "main.py"
with as_cwd(self.reload_path):
config = Config(
app="tests.test_config:asgi_app",
reload=True,
reload_dirs=[str(app_dir), str(app_first_dir)],
)
reloader = self._setup_reloader(config)
> assert self._reload_tester(touch_soon, reloader, app_file)
tests/supervisors/test_reload.py:203:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
tests/supervisors/test_reload.py:64: in _reload_tester
return next(reloader)
uvicorn/supervisors/basereload.py:64: in __next__
return self.should_restart()
uvicorn/supervisors/watchfilesreload.py:85: in should_restart
changes = next(self.watcher)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
watch_filter = None, debounce = 1600, step = 50, stop_event = <threading.Event object at 0x7f759a137730>
def watch(
*paths: Union[Path, str],
watch_filter: Optional[Callable[['Change', str], bool]] = DefaultFilter(),
debounce: int = 1_600,
step: int = 50,
stop_event: Optional['AbstractEvent'] = None,
rust_timeout: int = 5_000,
yield_on_timeout: bool = False,
debug: bool = False,
raise_interrupt: bool = True,
force_polling: Optional[bool] = None,
poll_delay_ms: int = 300,
recursive: bool = True,
) -> Generator[Set[FileChange], None, None]:
"""
Watch one or more paths and yield a set of changes whenever files change.
The paths watched can be directories or files, directories are watched recursively - changes in subdirectories
are also detected.
#### Force polling
Notify will fall back to file polling if it can't use file system notifications, but we also force notify
to us polling if the `force_polling` argument is `True`; if `force_polling` is unset (or `None`), we enable
force polling thus:
* if the `WATCHFILES_FORCE_POLLING` environment variable exists and is not empty:
* if the value is `false`, `disable` or `disabled`, force polling is disabled
* otherwise, force polling is enabled
* otherwise, we enable force polling only if we detect we're running on WSL (Windows Subsystem for Linux)
Args:
*paths: filesystem paths to watch.
watch_filter: callable used to filter out changes which are not important, you can either use a raw callable
or a [`BaseFilter`][watchfiles.BaseFilter] instance,
defaults to an instance of [`DefaultFilter`][watchfiles.DefaultFilter]. To keep all changes, use `None`.
debounce: maximum time in milliseconds to group changes over before yielding them.
step: time to wait for new changes in milliseconds, if no changes are detected in this time, and
at least one change has been detected, the changes are yielded.
stop_event: event to stop watching, if this is set, the generator will stop iteration,
this can be anything with an `is_set()` method which returns a bool, e.g. `threading.Event()`.
rust_timeout: maximum time in milliseconds to wait in the rust code for changes, `0` means no timeout.
yield_on_timeout: if `True`, the generator will yield upon timeout in rust even if no changes are detected.
debug: whether to print information about all filesystem changes in rust to stdout.
raise_interrupt: whether to re-raise `KeyboardInterrupt`s, or suppress the error and just stop iterating.
force_polling: See [Force polling](#force-polling) above.
poll_delay_ms: delay between polling for changes, only used if `force_polling=True`.
recursive: if `True`, watch for changes in sub-directories recursively, otherwise watch only for changes in the
top-level directory, default is `True`.
Yields:
The generator yields sets of [`FileChange`][watchfiles.main.FileChange]s.
```py title="Example of watch usage"
from watchfiles import watch
for changes in watch('./first/dir', './second/dir', raise_interrupt=False):
print(changes)
```
"""
force_polling = _default_force_polling(force_polling)
> with RustNotify([str(p) for p in paths], debug, force_polling, poll_delay_ms, recursive) as watcher:
E FileNotFoundError: OS file watch limit reached. about ["/tmp/pytest-of-marcelo/pytest-1/reload_directory1/app/css"]
../../../.pyenv/versions/uvicorn3.10/lib/python3.10/site-packages/watchfiles/main.py:119: FileNotFoundError
--------------------------------------------------------------------------- Captured stderr call ----------------------------------------------------------------------------
INFO: Will watch for changes in these directories: ['/tmp/pytest-of-marcelo/pytest-1/reload_directory1/app', '/tmp/pytest-of-marcelo/pytest-1/reload_directory1/app_first']
INFO: Started reloader process [28645] using WatchFiles
----------------------------------------------------------------------------- Captured log call -----------------------------------------------------------------------------
INFO uvicorn.error:config.py:353 Will watch for changes in these directories: ['/tmp/pytest-of-marcelo/pytest-1/reload_directory1/app', '/tmp/pytest-of-marcelo/pytest-1/reload_directory1/app_first']
INFO uvicorn.error:basereload.py:72 Started reloader process [28645] using WatchFiles
_________________________________________ TestBaseReload.test_should_not_reload_when_only_subdirectory_is_watched[WatchFilesReload] _________________________________________
self = <test_reload.TestBaseReload object at 0x7f759e5d7af0>, touch_soon = <function touch_soon.<locals>.start at 0x7f759a1a0f70>
@pytest.mark.parametrize(
"reloader_class", [StatReload, WatchGodReload, WatchFilesReload]
)
def test_should_not_reload_when_only_subdirectory_is_watched(
self, touch_soon
) -> None:
app_dir = self.reload_path / "app"
app_dir_file = self.reload_path / "app" / "src" / "main.py"
root_file = self.reload_path / "main.py"
config = Config(
app="tests.test_config:asgi_app",
reload=True,
reload_dirs=[str(app_dir)],
)
reloader = self._setup_reloader(config)
> assert self._reload_tester(touch_soon, reloader, app_dir_file)
tests/supervisors/test_reload.py:225:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
tests/supervisors/test_reload.py:64: in _reload_tester
return next(reloader)
uvicorn/supervisors/basereload.py:64: in __next__
return self.should_restart()
uvicorn/supervisors/watchfilesreload.py:85: in should_restart
changes = next(self.watcher)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
watch_filter = None, debounce = 1600, step = 50, stop_event = <threading.Event object at 0x7f759ddab280>
def watch(
*paths: Union[Path, str],
watch_filter: Optional[Callable[['Change', str], bool]] = DefaultFilter(),
debounce: int = 1_600,
step: int = 50,
stop_event: Optional['AbstractEvent'] = None,
rust_timeout: int = 5_000,
yield_on_timeout: bool = False,
debug: bool = False,
raise_interrupt: bool = True,
force_polling: Optional[bool] = None,
poll_delay_ms: int = 300,
recursive: bool = True,
) -> Generator[Set[FileChange], None, None]:
"""
Watch one or more paths and yield a set of changes whenever files change.
The paths watched can be directories or files, directories are watched recursively - changes in subdirectories
are also detected.
#### Force polling
Notify will fall back to file polling if it can't use file system notifications, but we also force notify
to us polling if the `force_polling` argument is `True`; if `force_polling` is unset (or `None`), we enable
force polling thus:
* if the `WATCHFILES_FORCE_POLLING` environment variable exists and is not empty:
* if the value is `false`, `disable` or `disabled`, force polling is disabled
* otherwise, force polling is enabled
* otherwise, we enable force polling only if we detect we're running on WSL (Windows Subsystem for Linux)
Args:
*paths: filesystem paths to watch.
watch_filter: callable used to filter out changes which are not important, you can either use a raw callable
or a [`BaseFilter`][watchfiles.BaseFilter] instance,
defaults to an instance of [`DefaultFilter`][watchfiles.DefaultFilter]. To keep all changes, use `None`.
debounce: maximum time in milliseconds to group changes over before yielding them.
step: time to wait for new changes in milliseconds, if no changes are detected in this time, and
at least one change has been detected, the changes are yielded.
stop_event: event to stop watching, if this is set, the generator will stop iteration,
this can be anything with an `is_set()` method which returns a bool, e.g. `threading.Event()`.
rust_timeout: maximum time in milliseconds to wait in the rust code for changes, `0` means no timeout.
yield_on_timeout: if `True`, the generator will yield upon timeout in rust even if no changes are detected.
debug: whether to print information about all filesystem changes in rust to stdout.
raise_interrupt: whether to re-raise `KeyboardInterrupt`s, or suppress the error and just stop iterating.
force_polling: See [Force polling](#force-polling) above.
poll_delay_ms: delay between polling for changes, only used if `force_polling=True`.
recursive: if `True`, watch for changes in sub-directories recursively, otherwise watch only for changes in the
top-level directory, default is `True`.
Yields:
The generator yields sets of [`FileChange`][watchfiles.main.FileChange]s.
```py title="Example of watch usage"
from watchfiles import watch
for changes in watch('./first/dir', './second/dir', raise_interrupt=False):
print(changes)
```
"""
force_polling = _default_force_polling(force_polling)
> with RustNotify([str(p) for p in paths], debug, force_polling, poll_delay_ms, recursive) as watcher:
E FileNotFoundError: OS file watch limit reached. about ["/home/marcelo/Development/encode/uvicorn/tests/__pycache__"]
../../../.pyenv/versions/uvicorn3.10/lib/python3.10/site-packages/watchfiles/main.py:119: FileNotFoundError
--------------------------------------------------------------------------- Captured stderr call ----------------------------------------------------------------------------
INFO: Will watch for changes in these directories: ['/tmp/pytest-of-marcelo/pytest-1/reload_directory1/app']
INFO: Started reloader process [28645] using WatchFiles
----------------------------------------------------------------------------- Captured log call -----------------------------------------------------------------------------
INFO uvicorn.error:config.py:353 Will watch for changes in these directories: ['/tmp/pytest-of-marcelo/pytest-1/reload_directory1/app']
INFO uvicorn.error:basereload.py:72 Started reloader process [28645] using WatchFiles
__________________________________________________________ TestBaseReload.test_override_defaults[WatchFilesReload] __________________________________________________________
self = <test_reload.TestBaseReload object at 0x7f759e5d7d00>, touch_soon = <function touch_soon.<locals>.start at 0x7f759a1a2e60>
@pytest.mark.parametrize("reloader_class", [WatchFilesReload, WatchGodReload])
def test_override_defaults(self, touch_soon) -> None:
dotted_file = self.reload_path / ".dotted"
dotted_dir_file = self.reload_path / ".dotted_dir" / "file.txt"
python_file = self.reload_path / "main.py"
with as_cwd(self.reload_path):
config = Config(
app="tests.test_config:asgi_app",
reload=True,
# We need to add *.txt otherwise no regular files will match
reload_includes=[".*", "*.txt"],
reload_excludes=["*.py"],
)
reloader = self._setup_reloader(config)
> assert self._reload_tester(touch_soon, reloader, dotted_file)
tests/supervisors/test_reload.py:248:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
tests/supervisors/test_reload.py:64: in _reload_tester
return next(reloader)
uvicorn/supervisors/basereload.py:64: in __next__
return self.should_restart()
uvicorn/supervisors/watchfilesreload.py:85: in should_restart
changes = next(self.watcher)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
watch_filter = None, debounce = 1600, step = 50, stop_event = <threading.Event object at 0x7f759ddbb850>
def watch(
*paths: Union[Path, str],
watch_filter: Optional[Callable[['Change', str], bool]] = DefaultFilter(),
debounce: int = 1_600,
step: int = 50,
stop_event: Optional['AbstractEvent'] = None,
rust_timeout: int = 5_000,
yield_on_timeout: bool = False,
debug: bool = False,
raise_interrupt: bool = True,
force_polling: Optional[bool] = None,
poll_delay_ms: int = 300,
recursive: bool = True,
) -> Generator[Set[FileChange], None, None]:
"""
Watch one or more paths and yield a set of changes whenever files change.
The paths watched can be directories or files, directories are watched recursively - changes in subdirectories
are also detected.
#### Force polling
Notify will fall back to file polling if it can't use file system notifications, but we also force notify
to us polling if the `force_polling` argument is `True`; if `force_polling` is unset (or `None`), we enable
force polling thus:
* if the `WATCHFILES_FORCE_POLLING` environment variable exists and is not empty:
* if the value is `false`, `disable` or `disabled`, force polling is disabled
* otherwise, force polling is enabled
* otherwise, we enable force polling only if we detect we're running on WSL (Windows Subsystem for Linux)
Args:
*paths: filesystem paths to watch.
watch_filter: callable used to filter out changes which are not important, you can either use a raw callable
or a [`BaseFilter`][watchfiles.BaseFilter] instance,
defaults to an instance of [`DefaultFilter`][watchfiles.DefaultFilter]. To keep all changes, use `None`.
debounce: maximum time in milliseconds to group changes over before yielding them.
step: time to wait for new changes in milliseconds, if no changes are detected in this time, and
at least one change has been detected, the changes are yielded.
stop_event: event to stop watching, if this is set, the generator will stop iteration,
this can be anything with an `is_set()` method which returns a bool, e.g. `threading.Event()`.
rust_timeout: maximum time in milliseconds to wait in the rust code for changes, `0` means no timeout.
yield_on_timeout: if `True`, the generator will yield upon timeout in rust even if no changes are detected.
debug: whether to print information about all filesystem changes in rust to stdout.
raise_interrupt: whether to re-raise `KeyboardInterrupt`s, or suppress the error and just stop iterating.
force_polling: See [Force polling](#force-polling) above.
poll_delay_ms: delay between polling for changes, only used if `force_polling=True`.
recursive: if `True`, watch for changes in sub-directories recursively, otherwise watch only for changes in the
top-level directory, default is `True`.
Yields:
The generator yields sets of [`FileChange`][watchfiles.main.FileChange]s.
```py title="Example of watch usage"
from watchfiles import watch
for changes in watch('./first/dir', './second/dir', raise_interrupt=False):
print(changes)
```
"""
force_polling = _default_force_polling(force_polling)
> with RustNotify([str(p) for p in paths], debug, force_polling, poll_delay_ms, recursive) as watcher:
E FileNotFoundError: OS file watch limit reached. about ["/tmp/pytest-of-marcelo/pytest-1/reload_directory1/app/css"]
../../../.pyenv/versions/uvicorn3.10/lib/python3.10/site-packages/watchfiles/main.py:119: FileNotFoundError
--------------------------------------------------------------------------- Captured stderr call ----------------------------------------------------------------------------
INFO: Will watch for changes in these directories: ['/tmp/pytest-of-marcelo/pytest-1/reload_directory1']
INFO: Started reloader process [28645] using WatchFiles
----------------------------------------------------------------------------- Captured log call -----------------------------------------------------------------------------
INFO uvicorn.error:config.py:353 Will watch for changes in these directories: ['/tmp/pytest-of-marcelo/pytest-1/reload_directory1']
INFO uvicorn.error:basereload.py:72 Started reloader process [28645] using WatchFiles
====================================================================== 9 failed, 439 passed in 51.93s =======================================================================
Example Code
No response
Watchfiles Output
No response
Operating System & Architecture
Linux-5.15.0-52-generic-x86_64-with-glibc2.31 #58~20.04.1-Ubuntu SMP Thu Oct 13 13:09:46 UTC 2022
Environment
No response
Python & Watchfiles Version
python: 3.10.5 (main, Jul 9 2022, 13:39:02) [GCC 9.4.0], watchfiles: 0.18.1
Rust & Cargo Version
No response
Thanks for reporting, do the errors go away if your revert to v18.0?
If my memory doesn't fail me, I was able to reproduce it yesterday with 0.15.0, and then I bumped it to 0.18.1. Assume that I can be mistaken.
I've downgraded to try, but as I said, "[...] happens from time to time", so I can't easily reproduce.
Okay, I see what's happening.
This is not actually "file not found", it's that you're hitting the OS file watching limit.
See the Error is FileNotFoundError: OS file watch limit reached. about
, which comes from
https://github.com/samuelcolvin/watchfiles/blob/94ef9038d39147deec17d1a56eb1e81d17a747b8/src/lib.rs#L57
Basically, the only time I've seen an error there was when the path didn't exist, so I created a PyFileNotFoundError
, obviously we should look at the error type or error message and use a different exception.
I'm 99.9% sure this won't be related to the recent v0.18.1 release.
So the next question is what should you do?
Either watchfiles or your test suite is watching too many files, or your IDE and other apps are watching lots of files, then watchfiles is just going over the limit.
If the problem is just that you're watching lots of files legitimately, you can increase max_user_watches
, see here for example.
If the problem is that watchfiles is somehow not "un-watching" fies when the watcher is dropped, we'll need to make a change in watchfiles. I'll do some investigation.
If the problem is that watchfiles is somehow not "un-watching" fies when the watcher is dropped, we'll need to make a change in watchfiles. I'll do some investigation.
Thanks.
Okay, I've fixed the wrong-error issue in #208.
I've also done some digging into how many files watchfiles is watching, using inotify-info.
I ran the following script, then monitored the number of "watches" from the "python3.10" process using inotify-info
.
import watchfiles
print(f'Watchfiles v{watchfiles.__version__}')
for i in range(100):
for changes in watchfiles.watch('/home/samuel/code/pydantic/', rust_timeout=1, yield_on_timeout=True):
print(i, changes)
break
I don't see the number of watches increase, also there are 127196
files in the pydantic
directory (multiple venvs), so if the watches weren't being dropped between steps, we should hit the OS limit fairly quickly.
I think the problem is probably with either a test which watches a big directory, or your system is close to the watch limit.
Maybe you could have a quick look with inotify-info
?
Am I supposed to use inotify-info
on the python process running pytest
?
I just ran it and it showed which processes where holding the most watches.
Hi, I've been seeing a similar issue where I suspect the root cause is not a missing file/folder. I was wondering if it would be possible to cut a new release which includes the fix
I think your link is broken and you mean #208.
I'll try to make a new release soon.
done, building now, with luck should be released in a ~30mins.
I think your link is broken and you mean #208.
Hahah yes, oops.
done, building now, with luck should be released in a ~30mins.
Thank you so very much!