2022-05-23 00:16:32 +04:00

185 lines
5.5 KiB
Python

# Copyright 2018-2022 Streamlit Inc.
#
# 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 typing import Callable, Optional, Type, Union
import click
import streamlit.watcher
from streamlit import config
from streamlit import env_util
from streamlit.logger import get_logger
from streamlit.watcher.polling_path_watcher import PollingPathWatcher
LOGGER = get_logger(__name__)
try:
# Check if the watchdog module is installed.
from streamlit.watcher.event_based_path_watcher import EventBasedPathWatcher
watchdog_available = True
except ImportError:
watchdog_available = False
# Stub the EventBasedPathWatcher so it can be mocked by tests
class EventBasedPathWatcher: # type: ignore
pass
# 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: Optional[str] = None,
allow_nonexistent: bool = False,
):
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 report_watchdog_availability():
if not watchdog_available:
if not config.get_option("global.disableWatchdogWarning"):
msg = "\n $ xcode-select --install" if env_util.IS_DARWIN else ""
click.secho(
" %s" % "For better performance, install the Watchdog module:",
fg="blue",
bold=True,
)
click.secho(
"""%s
$ pip install watchdog
"""
% msg
)
def _watch_path(
path: str,
on_path_changed: Callable[[str], None],
watcher_type: Optional[str] = None,
*, # keyword-only arguments:
glob_pattern: Optional[str] = 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: Optional[str] = None,
) -> bool:
return _watch_path(path, on_file_changed, watcher_type)
def watch_dir(
path: str,
on_dir_changed: Callable[[str], None],
watcher_type: Optional[str] = None,
*, # keyword-only arguments:
glob_pattern: Optional[str] = None,
allow_nonexistent: bool = False,
) -> bool:
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 == "auto":
if watchdog_available:
return EventBasedPathWatcher
else:
return PollingPathWatcher
elif watcher_type == "watchdog" and watchdog_available:
return EventBasedPathWatcher
elif watcher_type == "poll":
return PollingPathWatcher
else:
return NoOpPathWatcher