181 lines
5.5 KiB
Python
181 lines
5.5 KiB
Python
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from typing import Callable, Union
|
|
|
|
import streamlit.watcher
|
|
from streamlit import cli_util, config, env_util
|
|
from streamlit.watcher.polling_path_watcher import PollingPathWatcher
|
|
|
|
|
|
# local_sources_watcher.py caches the return value of
|
|
# get_default_path_watcher_class(), so it needs to differentiate between the
|
|
# cases where it:
|
|
# 1. has yet to call get_default_path_watcher_class()
|
|
# 2. has called get_default_path_watcher_class(), which returned that no
|
|
# path watcher should be installed.
|
|
# This forces us to define this stub class since the cached value equaling
|
|
# None corresponds to case 1 above.
|
|
class NoOpPathWatcher:
|
|
def __init__(
|
|
self,
|
|
_path_str: str,
|
|
_on_changed: Callable[[str], None],
|
|
*, # keyword-only arguments:
|
|
glob_pattern: str | None = None,
|
|
allow_nonexistent: bool = False,
|
|
) -> None:
|
|
pass
|
|
|
|
|
|
# EventBasedPathWatcher will be a stub and have no functional
|
|
# implementation if its import failed (due to missing watchdog module),
|
|
# so we can't reference it directly in this type.
|
|
PathWatcherType = Union[
|
|
type["streamlit.watcher.event_based_path_watcher.EventBasedPathWatcher"],
|
|
type[PollingPathWatcher],
|
|
type[NoOpPathWatcher],
|
|
]
|
|
|
|
|
|
def _is_watchdog_available() -> bool:
|
|
"""Check if the watchdog module is installed."""
|
|
try:
|
|
import watchdog # noqa: F401
|
|
|
|
return True
|
|
except ImportError:
|
|
return False
|
|
|
|
|
|
def report_watchdog_availability() -> None:
|
|
if (
|
|
config.get_option("server.fileWatcherType") not in ["poll", "none"]
|
|
and not _is_watchdog_available()
|
|
):
|
|
msg = "\n $ xcode-select --install" if env_util.IS_DARWIN else ""
|
|
|
|
cli_util.print_to_cli(
|
|
" For better performance, install the Watchdog module:",
|
|
fg="blue",
|
|
bold=True,
|
|
)
|
|
cli_util.print_to_cli(
|
|
f"""{msg}
|
|
$ pip install watchdog
|
|
"""
|
|
)
|
|
|
|
|
|
def _watch_path(
|
|
path: str,
|
|
on_path_changed: Callable[[str], None],
|
|
watcher_type: str | None = None,
|
|
*, # keyword-only arguments:
|
|
glob_pattern: str | None = None,
|
|
allow_nonexistent: bool = False,
|
|
) -> bool:
|
|
"""Create a PathWatcher for the given path if we have a viable
|
|
PathWatcher class.
|
|
|
|
Parameters
|
|
----------
|
|
path
|
|
Path to watch.
|
|
on_path_changed
|
|
Function that's called when the path changes.
|
|
watcher_type
|
|
Optional watcher_type string. If None, it will default to the
|
|
'server.fileWatcherType` config option.
|
|
glob_pattern
|
|
Optional glob pattern to use when watching a directory. If set, only
|
|
files matching the pattern will be counted as being created/deleted
|
|
within the watched directory.
|
|
allow_nonexistent
|
|
If True, allow the file or directory at the given path to be
|
|
nonexistent.
|
|
|
|
Returns
|
|
-------
|
|
bool
|
|
True if the path is being watched, or False if we have no
|
|
PathWatcher class.
|
|
"""
|
|
if watcher_type is None:
|
|
watcher_type = config.get_option("server.fileWatcherType")
|
|
|
|
watcher_class = get_path_watcher_class(watcher_type)
|
|
if watcher_class is NoOpPathWatcher:
|
|
return False
|
|
|
|
watcher_class(
|
|
path,
|
|
on_path_changed,
|
|
glob_pattern=glob_pattern,
|
|
allow_nonexistent=allow_nonexistent,
|
|
)
|
|
return True
|
|
|
|
|
|
def watch_file(
|
|
path: str,
|
|
on_file_changed: Callable[[str], None],
|
|
watcher_type: str | None = None,
|
|
) -> bool:
|
|
return _watch_path(path, on_file_changed, watcher_type)
|
|
|
|
|
|
def watch_dir(
|
|
path: str,
|
|
on_dir_changed: Callable[[str], None],
|
|
watcher_type: str | None = None,
|
|
*, # keyword-only arguments:
|
|
glob_pattern: str | None = None,
|
|
allow_nonexistent: bool = False,
|
|
) -> bool:
|
|
# Add a trailing slash to the path to ensure
|
|
# that its interpreted as a directory.
|
|
path = os.path.join(path, "")
|
|
|
|
return _watch_path(
|
|
path,
|
|
on_dir_changed,
|
|
watcher_type,
|
|
glob_pattern=glob_pattern,
|
|
allow_nonexistent=allow_nonexistent,
|
|
)
|
|
|
|
|
|
def get_default_path_watcher_class() -> PathWatcherType:
|
|
"""Return the class to use for path changes notifications, based on the
|
|
server.fileWatcherType config option.
|
|
"""
|
|
return get_path_watcher_class(config.get_option("server.fileWatcherType"))
|
|
|
|
|
|
def get_path_watcher_class(watcher_type: str) -> PathWatcherType:
|
|
"""Return the PathWatcher class that corresponds to the given watcher_type
|
|
string. Acceptable values are 'auto', 'watchdog', 'poll' and 'none'.
|
|
"""
|
|
if watcher_type in {"watchdog", "auto"} and _is_watchdog_available():
|
|
# Lazy-import this module to prevent unnecessary imports of the watchdog package.
|
|
from streamlit.watcher.event_based_path_watcher import EventBasedPathWatcher
|
|
|
|
return EventBasedPathWatcher
|
|
if watcher_type in {"auto", "poll"}:
|
|
return PollingPathWatcher
|
|
return NoOpPathWatcher
|