Esempio n. 1
0
 def test_md5_calculation_opens_file_with_rb(self):
     # This tests implementation :( . But since the issue this is addressing
     # could easily come back to bite us if a distracted coder tweaks the
     # implementation, I'm putting this here anyway.
     with patch("streamlit.watcher.util.open", mock_open(read_data=b"hello")) as m:
         md5 = util.calc_md5_with_blocking_retries("foo")
         m.assert_called_once_with("foo", "rb")
    def add_path_change_listener(
        self,
        path: str,
        callback: Callable[[str], None],
        *,  # keyword-only arguments:
        glob_pattern: Optional[str] = None,
        allow_nonexistent: bool = False,
    ) -> None:
        """Add a path to this object's event filter."""
        with self._lock:
            watched_path = self._watched_paths.get(path, None)
            if watched_path is None:
                md5 = util.calc_md5_with_blocking_retries(
                    path,
                    glob_pattern=glob_pattern,
                    allow_nonexistent=allow_nonexistent,
                )
                modification_time = util.path_modification_time(
                    path, allow_nonexistent)
                watched_path = WatchedPath(
                    md5=md5,
                    modification_time=modification_time,
                    glob_pattern=glob_pattern,
                    allow_nonexistent=allow_nonexistent,
                )
                self._watched_paths[path] = watched_path

            watched_path.on_changed.connect(callback, weak=False)
    def handle_path_change_event(self, event: events.FileSystemEvent) -> None:
        """Handle when a path (corresponding to a file or dir) is changed.

        The events that can call this are modification, creation or moved
        events.
        """

        # Check for both modified and moved files, because many programs write
        # to a backup file then rename (i.e. move) it.
        if event.event_type == events.EVENT_TYPE_MODIFIED:
            changed_path = event.src_path
        elif event.event_type == events.EVENT_TYPE_MOVED:
            LOGGER.debug("Move event: src %s; dest %s", event.src_path,
                         event.dest_path)
            changed_path = event.dest_path
        # On OSX with VI, on save, the file is deleted, the swap file is
        # modified and then the original file is created hence why we
        # capture EVENT_TYPE_CREATED
        elif event.event_type == events.EVENT_TYPE_CREATED:
            changed_path = event.src_path
        else:
            LOGGER.debug("Don't care about event type %s", event.event_type)
            return

        changed_path = os.path.abspath(changed_path)

        changed_path_info = self._watched_paths.get(changed_path, None)
        if changed_path_info is None:
            LOGGER.debug(
                "Ignoring changed path %s.\nWatched_paths: %s",
                changed_path,
                self._watched_paths,
            )
            return

        modification_time = util.path_modification_time(
            changed_path, changed_path_info.allow_nonexistent)
        if modification_time == changed_path_info.modification_time:
            LOGGER.debug("File/dir timestamp did not change: %s", changed_path)
            return

        changed_path_info.modification_time = modification_time

        new_md5 = util.calc_md5_with_blocking_retries(
            changed_path,
            glob_pattern=changed_path_info.glob_pattern,
            allow_nonexistent=changed_path_info.allow_nonexistent,
        )
        if new_md5 == changed_path_info.md5:
            LOGGER.debug("File/dir MD5 did not change: %s", changed_path)
            return

        LOGGER.debug("File/dir MD5 changed: %s", changed_path)
        changed_path_info.md5 = new_md5
        changed_path_info.on_changed.send(changed_path)
    def handle_file_change_event(self, event):
        """Handle when file is changed.

        The events that can call this are modification, creation or moved
        events.

        Parameters
        ----------
        event : FileSystemEvent
            The event object representing the file system event.

        """
        if event.is_directory:
            return

        # Check for both modified and moved files, because many programs write
        # to a backup file then rename (i.e. move) it.
        if event.event_type == events.EVENT_TYPE_MODIFIED:
            file_path = event.src_path
        # On OSX with VI, on save, the file is deleted, the swap file is
        # modified and then the original file is created hence why we
        # capture EVENT_TYPE_CREATED
        elif event.event_type == events.EVENT_TYPE_CREATED:
            file_path = event.src_path
        elif event.event_type == events.EVENT_TYPE_MOVED:
            LOGGER.debug("Move event: src %s; dest %s", event.src_path, event.dest_path)
            file_path = event.dest_path
        else:
            LOGGER.debug("Don't care about event type %s", event.event_type),
            return

        file_path = os.path.abspath(file_path)

        file_info = self._watched_files.get(file_path, None)
        if file_info is None:
            LOGGER.debug(
                "Ignoring file %s.\nWatched_files: %s", file_path, self._watched_files
            )
            return

        modification_time = os.stat(file_path).st_mtime
        if modification_time == file_info.modification_time:
            LOGGER.debug("File timestamp did not change: %s", file_path)
            return

        file_info.modification_time = modification_time

        new_md5 = util.calc_md5_with_blocking_retries(file_path)
        if new_md5 == file_info.md5:
            LOGGER.debug("File MD5 did not change: %s", file_path)
            return

        LOGGER.debug("File MD5 changed: %s", file_path)
        file_info.md5 = new_md5
        file_info.on_file_changed.send(file_path)
    def add_file_change_listener(self, file_path, callback):
        """Add a file to this object's event filter.

        Parameters
        ----------
        file_path : str
        callback : callable

        """
        with self._lock:
            watched_file = self._watched_files.get(file_path, None)
            if watched_file is None:
                md5 = util.calc_md5_with_blocking_retries(file_path)
                modification_time = os.stat(file_path).st_mtime
                watched_file = WatchedFile(md5=md5, modification_time=modification_time)
                self._watched_files[file_path] = watched_file

            watched_file.on_file_changed.connect(callback, weak=False)
Esempio n. 6
0
    def __init__(self, file_path, on_file_changed):
        """Constructor.

        Arguments
        ---------
        file_path : str
            Absolute path of the file to watch.

        on_file_changed : callable
            Function to call when the file changes. This function should
            take the changed file's path as a parameter.

        """
        self._file_path = file_path
        self._on_file_changed = on_file_changed

        self._active = True
        self._modification_time = os.stat(self._file_path).st_mtime
        self._md5 = util.calc_md5_with_blocking_retries(self._file_path)
        self._schedule()
Esempio n. 7
0
    def _check_if_file_changed(self) -> None:
        if not self._active:
            # Don't call self._schedule()
            return

        modification_time = os.stat(self._file_path).st_mtime
        if modification_time <= self._modification_time:
            self._schedule()
            return

        self._modification_time = modification_time

        md5 = util.calc_md5_with_blocking_retries(self._file_path)
        if md5 == self._md5:
            self._schedule()
            return

        self._md5 = md5

        LOGGER.debug("Change detected: %s", self._file_path)
        self._on_file_changed(self._file_path)

        self._schedule()
Esempio n. 8
0
    def __init__(self, file_path: str, on_file_changed: Callable[[str], None]):
        """Constructor.

        You do not need to retain a reference to a PollingFileWatcher to
        prevent it from being garbage collected. (The global _executor object
        retains references to all active instances.)

        Arguments
        ---------
        file_path
            Absolute path of the file to watch.

        on_file_changed
            Function to call when the file changes. This function should
            take the changed file's path as a parameter.

        """
        self._file_path = file_path
        self._on_file_changed = on_file_changed

        self._active = True
        self._modification_time = os.stat(self._file_path).st_mtime
        self._md5 = util.calc_md5_with_blocking_retries(self._file_path)
        self._schedule()
Esempio n. 9
0
 def test_md5_calculation_succeeds_with_bytes_input(self):
     with patch("streamlit.watcher.util.open",
                mock_open(read_data=b"hello")) as m:
         md5 = util.calc_md5_with_blocking_retries("foo")
         self.assertEqual(md5, "5d41402abc4b2a76b9719d911017c592")
Esempio n. 10
0
 def test_md5_calculation_allow_nonexistent(self):
     md5 = util.calc_md5_with_blocking_retries("hello", allow_nonexistent=True)
     self.assertEqual(md5, "5d41402abc4b2a76b9719d911017c592")
Esempio n. 11
0
    def test_md5_calculation_can_pass_glob(self, mock_stable_dir_identifier):
        mock_stable_dir_identifier.return_value = "hello"

        md5 = util.calc_md5_with_blocking_retries("foo", glob_pattern="*.py")
        mock_stable_dir_identifier.assert_called_once_with("foo", "*.py")
Esempio n. 12
0
    def test_md5_calculation_succeeds_with_dir_input(self, mock_stable_dir_identifier):
        mock_stable_dir_identifier.return_value = "hello"

        md5 = util.calc_md5_with_blocking_retries("foo")
        self.assertEqual(md5, "5d41402abc4b2a76b9719d911017c592")
        mock_stable_dir_identifier.assert_called_once_with("foo", "*")