added podman, json and yaml
This commit is contained in:
140
venv/lib/python3.11/site-packages/watchdog/utils/__init__.py
Normal file
140
venv/lib/python3.11/site-packages/watchdog/utils/__init__.py
Normal file
@ -0,0 +1,140 @@
|
||||
# coding: utf-8
|
||||
#
|
||||
# Copyright 2011 Yesudeep Mangalapilly <yesudeep@gmail.com>
|
||||
# Copyright 2012 Google, Inc & contributors.
|
||||
#
|
||||
# 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.
|
||||
|
||||
|
||||
"""
|
||||
:module: watchdog.utils
|
||||
:synopsis: Utility classes and functions.
|
||||
:author: yesudeep@google.com (Yesudeep Mangalapilly)
|
||||
:author: contact@tiger-222.fr (Mickaël Schoentgen)
|
||||
|
||||
Classes
|
||||
-------
|
||||
.. autoclass:: BaseThread
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:inherited-members:
|
||||
|
||||
"""
|
||||
import sys
|
||||
import threading
|
||||
|
||||
|
||||
class UnsupportedLibc(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class WatchdogShutdown(Exception):
|
||||
"""
|
||||
Semantic exception used to signal an external shutdown event.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class BaseThread(threading.Thread):
|
||||
""" Convenience class for creating stoppable threads. """
|
||||
|
||||
def __init__(self):
|
||||
threading.Thread.__init__(self)
|
||||
if hasattr(self, 'daemon'):
|
||||
self.daemon = True
|
||||
else:
|
||||
self.setDaemon(True)
|
||||
self._stopped_event = threading.Event()
|
||||
|
||||
if not hasattr(self._stopped_event, 'is_set'):
|
||||
self._stopped_event.is_set = self._stopped_event.isSet
|
||||
|
||||
@property
|
||||
def stopped_event(self):
|
||||
return self._stopped_event
|
||||
|
||||
def should_keep_running(self):
|
||||
"""Determines whether the thread should continue running."""
|
||||
return not self._stopped_event.is_set()
|
||||
|
||||
def on_thread_stop(self):
|
||||
"""Override this method instead of :meth:`stop()`.
|
||||
:meth:`stop()` calls this method.
|
||||
|
||||
This method is called immediately after the thread is signaled to stop.
|
||||
"""
|
||||
pass
|
||||
|
||||
def stop(self):
|
||||
"""Signals the thread to stop."""
|
||||
self._stopped_event.set()
|
||||
self.on_thread_stop()
|
||||
|
||||
def on_thread_start(self):
|
||||
"""Override this method instead of :meth:`start()`. :meth:`start()`
|
||||
calls this method.
|
||||
|
||||
This method is called right before this thread is started and this
|
||||
object’s run() method is invoked.
|
||||
"""
|
||||
pass
|
||||
|
||||
def start(self):
|
||||
self.on_thread_start()
|
||||
threading.Thread.start(self)
|
||||
|
||||
|
||||
def load_module(module_name):
|
||||
"""Imports a module given its name and returns a handle to it."""
|
||||
try:
|
||||
__import__(module_name)
|
||||
except ImportError:
|
||||
raise ImportError('No module named %s' % module_name)
|
||||
return sys.modules[module_name]
|
||||
|
||||
|
||||
def load_class(dotted_path):
|
||||
"""Loads and returns a class definition provided a dotted path
|
||||
specification the last part of the dotted path is the class name
|
||||
and there is at least one module name preceding the class name.
|
||||
|
||||
Notes:
|
||||
You will need to ensure that the module you are trying to load
|
||||
exists in the Python path.
|
||||
|
||||
Examples:
|
||||
- module.name.ClassName # Provided module.name is in the Python path.
|
||||
- module.ClassName # Provided module is in the Python path.
|
||||
|
||||
What won't work:
|
||||
- ClassName
|
||||
- modle.name.ClassName # Typo in module name.
|
||||
- module.name.ClasNam # Typo in classname.
|
||||
"""
|
||||
dotted_path_split = dotted_path.split('.')
|
||||
if len(dotted_path_split) > 1:
|
||||
klass_name = dotted_path_split[-1]
|
||||
module_name = '.'.join(dotted_path_split[:-1])
|
||||
|
||||
module = load_module(module_name)
|
||||
if hasattr(module, klass_name):
|
||||
klass = getattr(module, klass_name)
|
||||
return klass
|
||||
# Finally create and return an instance of the class
|
||||
# return klass(*args, **kwargs)
|
||||
else:
|
||||
raise AttributeError('Module %s does not have class attribute %s' % (
|
||||
module_name, klass_name))
|
||||
else:
|
||||
raise ValueError(
|
||||
'Dotted module path %s must contain a module name and a classname' % dotted_path)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
102
venv/lib/python3.11/site-packages/watchdog/utils/bricks.py
Normal file
102
venv/lib/python3.11/site-packages/watchdog/utils/bricks.py
Normal file
@ -0,0 +1,102 @@
|
||||
# coding: utf-8
|
||||
#
|
||||
# Copyright 2011 Yesudeep Mangalapilly <yesudeep@gmail.com>
|
||||
# Copyright 2012 Google, Inc & contributors.
|
||||
#
|
||||
# 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.
|
||||
|
||||
|
||||
"""
|
||||
Utility collections or "bricks".
|
||||
|
||||
:module: watchdog.utils.bricks
|
||||
:author: yesudeep@google.com (Yesudeep Mangalapilly)
|
||||
:author: lalinsky@gmail.com (Lukáš Lalinský)
|
||||
:author: python@rcn.com (Raymond Hettinger)
|
||||
:author: contact@tiger-222.fr (Mickaël Schoentgen)
|
||||
|
||||
Classes
|
||||
=======
|
||||
.. autoclass:: OrderedSetQueue
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:inherited-members:
|
||||
|
||||
.. autoclass:: OrderedSet
|
||||
|
||||
"""
|
||||
|
||||
import queue
|
||||
|
||||
|
||||
class SkipRepeatsQueue(queue.Queue):
|
||||
|
||||
"""Thread-safe implementation of an special queue where a
|
||||
put of the last-item put'd will be dropped.
|
||||
|
||||
The implementation leverages locking already implemented in the base class
|
||||
redefining only the primitives.
|
||||
|
||||
Queued items must be immutable and hashable so that they can be used
|
||||
as dictionary keys. You must implement **only read-only properties** and
|
||||
the :meth:`Item.__hash__()`, :meth:`Item.__eq__()`, and
|
||||
:meth:`Item.__ne__()` methods for items to be hashable.
|
||||
|
||||
An example implementation follows::
|
||||
|
||||
class Item:
|
||||
def __init__(self, a, b):
|
||||
self._a = a
|
||||
self._b = b
|
||||
|
||||
@property
|
||||
def a(self):
|
||||
return self._a
|
||||
|
||||
@property
|
||||
def b(self):
|
||||
return self._b
|
||||
|
||||
def _key(self):
|
||||
return (self._a, self._b)
|
||||
|
||||
def __eq__(self, item):
|
||||
return self._key() == item._key()
|
||||
|
||||
def __ne__(self, item):
|
||||
return self._key() != item._key()
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self._key())
|
||||
|
||||
based on the OrderedSetQueue below
|
||||
"""
|
||||
|
||||
def _init(self, maxsize):
|
||||
super()._init(maxsize)
|
||||
self._last_item = None
|
||||
|
||||
def _put(self, item):
|
||||
if self._last_item is None or item != self._last_item:
|
||||
super()._put(item)
|
||||
self._last_item = item
|
||||
else:
|
||||
# `put` increments `unfinished_tasks` even if we did not put
|
||||
# anything into the queue here
|
||||
self.unfinished_tasks -= 1
|
||||
|
||||
def _get(self):
|
||||
item = super()._get()
|
||||
if item is self._last_item:
|
||||
self._last_item = None
|
||||
return item
|
||||
@ -0,0 +1,83 @@
|
||||
# coding: utf-8
|
||||
#
|
||||
# Copyright 2014 Thomas Amland <thomas.amland@gmail.com>
|
||||
#
|
||||
# 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.
|
||||
|
||||
import time
|
||||
import threading
|
||||
from collections import deque
|
||||
|
||||
|
||||
class DelayedQueue:
|
||||
|
||||
def __init__(self, delay):
|
||||
self.delay_sec = delay
|
||||
self._lock = threading.Lock()
|
||||
self._not_empty = threading.Condition(self._lock)
|
||||
self._queue = deque()
|
||||
self._closed = False
|
||||
|
||||
def put(self, element, delay=False):
|
||||
"""Add element to queue."""
|
||||
self._lock.acquire()
|
||||
self._queue.append((element, time.time(), delay))
|
||||
self._not_empty.notify()
|
||||
self._lock.release()
|
||||
|
||||
def close(self):
|
||||
"""Close queue, indicating no more items will be added."""
|
||||
self._closed = True
|
||||
# Interrupt the blocking _not_empty.wait() call in get
|
||||
self._not_empty.acquire()
|
||||
self._not_empty.notify()
|
||||
self._not_empty.release()
|
||||
|
||||
def get(self):
|
||||
"""Remove and return an element from the queue, or this queue has been
|
||||
closed raise the Closed exception.
|
||||
"""
|
||||
while True:
|
||||
# wait for element to be added to queue
|
||||
self._not_empty.acquire()
|
||||
while len(self._queue) == 0 and not self._closed:
|
||||
self._not_empty.wait()
|
||||
|
||||
if self._closed:
|
||||
self._not_empty.release()
|
||||
return None
|
||||
head, insert_time, delay = self._queue[0]
|
||||
self._not_empty.release()
|
||||
|
||||
# wait for delay if required
|
||||
if delay:
|
||||
time_left = insert_time + self.delay_sec - time.time()
|
||||
while time_left > 0:
|
||||
time.sleep(time_left)
|
||||
time_left = insert_time + self.delay_sec - time.time()
|
||||
|
||||
# return element if it's still in the queue
|
||||
with self._lock:
|
||||
if len(self._queue) > 0 and self._queue[0][0] is head:
|
||||
self._queue.popleft()
|
||||
return head
|
||||
|
||||
def remove(self, predicate):
|
||||
"""Remove and return the first items for which predicate is True,
|
||||
ignoring delay."""
|
||||
with self._lock:
|
||||
for i, (elem, t, delay) in enumerate(self._queue):
|
||||
if predicate(elem):
|
||||
del self._queue[i]
|
||||
return elem
|
||||
return None
|
||||
370
venv/lib/python3.11/site-packages/watchdog/utils/dirsnapshot.py
Normal file
370
venv/lib/python3.11/site-packages/watchdog/utils/dirsnapshot.py
Normal file
@ -0,0 +1,370 @@
|
||||
# coding: utf-8
|
||||
#
|
||||
# Copyright 2011 Yesudeep Mangalapilly <yesudeep@gmail.com>
|
||||
# Copyright 2012 Google, Inc & contributors.
|
||||
# Copyright 2014 Thomas Amland <thomas.amland@gmail.com>
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""
|
||||
:module: watchdog.utils.dirsnapshot
|
||||
:synopsis: Directory snapshots and comparison.
|
||||
:author: yesudeep@google.com (Yesudeep Mangalapilly)
|
||||
:author: contact@tiger-222.fr (Mickaël Schoentgen)
|
||||
|
||||
.. ADMONITION:: Where are the moved events? They "disappeared"
|
||||
|
||||
This implementation does not take partition boundaries
|
||||
into consideration. It will only work when the directory
|
||||
tree is entirely on the same file system. More specifically,
|
||||
any part of the code that depends on inode numbers can
|
||||
break if partition boundaries are crossed. In these cases,
|
||||
the snapshot diff will represent file/directory movement as
|
||||
created and deleted events.
|
||||
|
||||
Classes
|
||||
-------
|
||||
.. autoclass:: DirectorySnapshot
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: DirectorySnapshotDiff
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: EmptyDirectorySnapshot
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
"""
|
||||
|
||||
import errno
|
||||
import os
|
||||
from stat import S_ISDIR
|
||||
|
||||
|
||||
class DirectorySnapshotDiff:
|
||||
"""
|
||||
Compares two directory snapshots and creates an object that represents
|
||||
the difference between the two snapshots.
|
||||
|
||||
:param ref:
|
||||
The reference directory snapshot.
|
||||
:type ref:
|
||||
:class:`DirectorySnapshot`
|
||||
:param snapshot:
|
||||
The directory snapshot which will be compared
|
||||
with the reference snapshot.
|
||||
:type snapshot:
|
||||
:class:`DirectorySnapshot`
|
||||
:param ignore_device:
|
||||
A boolean indicating whether to ignore the device id or not.
|
||||
By default, a file may be uniquely identified by a combination of its first
|
||||
inode and its device id. The problem is that the device id may (or may not)
|
||||
change between system boots. This problem would cause the DirectorySnapshotDiff
|
||||
to think a file has been deleted and created again but it would be the
|
||||
exact same file.
|
||||
Set to True only if you are sure you will always use the same device.
|
||||
:type ignore_device:
|
||||
:class:`bool`
|
||||
"""
|
||||
|
||||
def __init__(self, ref, snapshot, ignore_device=False):
|
||||
created = snapshot.paths - ref.paths
|
||||
deleted = ref.paths - snapshot.paths
|
||||
|
||||
if ignore_device:
|
||||
def get_inode(directory, full_path):
|
||||
return directory.inode(full_path)[0]
|
||||
else:
|
||||
def get_inode(directory, full_path):
|
||||
return directory.inode(full_path)
|
||||
|
||||
# check that all unchanged paths have the same inode
|
||||
for path in ref.paths & snapshot.paths:
|
||||
if get_inode(ref, path) != get_inode(snapshot, path):
|
||||
created.add(path)
|
||||
deleted.add(path)
|
||||
|
||||
# find moved paths
|
||||
moved = set()
|
||||
for path in set(deleted):
|
||||
inode = ref.inode(path)
|
||||
new_path = snapshot.path(inode)
|
||||
if new_path:
|
||||
# file is not deleted but moved
|
||||
deleted.remove(path)
|
||||
moved.add((path, new_path))
|
||||
|
||||
for path in set(created):
|
||||
inode = snapshot.inode(path)
|
||||
old_path = ref.path(inode)
|
||||
if old_path:
|
||||
created.remove(path)
|
||||
moved.add((old_path, path))
|
||||
|
||||
# find modified paths
|
||||
# first check paths that have not moved
|
||||
modified = set()
|
||||
for path in ref.paths & snapshot.paths:
|
||||
if get_inode(ref, path) == get_inode(snapshot, path):
|
||||
if ref.mtime(path) != snapshot.mtime(path) or ref.size(path) != snapshot.size(path):
|
||||
modified.add(path)
|
||||
|
||||
for (old_path, new_path) in moved:
|
||||
if ref.mtime(old_path) != snapshot.mtime(new_path) or ref.size(old_path) != snapshot.size(new_path):
|
||||
modified.add(old_path)
|
||||
|
||||
self._dirs_created = [path for path in created if snapshot.isdir(path)]
|
||||
self._dirs_deleted = [path for path in deleted if ref.isdir(path)]
|
||||
self._dirs_modified = [path for path in modified if ref.isdir(path)]
|
||||
self._dirs_moved = [(frm, to) for (frm, to) in moved if ref.isdir(frm)]
|
||||
|
||||
self._files_created = list(created - set(self._dirs_created))
|
||||
self._files_deleted = list(deleted - set(self._dirs_deleted))
|
||||
self._files_modified = list(modified - set(self._dirs_modified))
|
||||
self._files_moved = list(moved - set(self._dirs_moved))
|
||||
|
||||
def __str__(self):
|
||||
return self.__repr__()
|
||||
|
||||
def __repr__(self):
|
||||
fmt = (
|
||||
'<{0} files(created={1}, deleted={2}, modified={3}, moved={4}),'
|
||||
' folders(created={5}, deleted={6}, modified={7}, moved={8})>'
|
||||
)
|
||||
return fmt.format(
|
||||
type(self).__name__,
|
||||
len(self._files_created),
|
||||
len(self._files_deleted),
|
||||
len(self._files_modified),
|
||||
len(self._files_moved),
|
||||
len(self._dirs_created),
|
||||
len(self._dirs_deleted),
|
||||
len(self._dirs_modified),
|
||||
len(self._dirs_moved)
|
||||
)
|
||||
|
||||
@property
|
||||
def files_created(self):
|
||||
"""List of files that were created."""
|
||||
return self._files_created
|
||||
|
||||
@property
|
||||
def files_deleted(self):
|
||||
"""List of files that were deleted."""
|
||||
return self._files_deleted
|
||||
|
||||
@property
|
||||
def files_modified(self):
|
||||
"""List of files that were modified."""
|
||||
return self._files_modified
|
||||
|
||||
@property
|
||||
def files_moved(self):
|
||||
"""
|
||||
List of files that were moved.
|
||||
|
||||
Each event is a two-tuple the first item of which is the path
|
||||
that has been renamed to the second item in the tuple.
|
||||
"""
|
||||
return self._files_moved
|
||||
|
||||
@property
|
||||
def dirs_modified(self):
|
||||
"""
|
||||
List of directories that were modified.
|
||||
"""
|
||||
return self._dirs_modified
|
||||
|
||||
@property
|
||||
def dirs_moved(self):
|
||||
"""
|
||||
List of directories that were moved.
|
||||
|
||||
Each event is a two-tuple the first item of which is the path
|
||||
that has been renamed to the second item in the tuple.
|
||||
"""
|
||||
return self._dirs_moved
|
||||
|
||||
@property
|
||||
def dirs_deleted(self):
|
||||
"""
|
||||
List of directories that were deleted.
|
||||
"""
|
||||
return self._dirs_deleted
|
||||
|
||||
@property
|
||||
def dirs_created(self):
|
||||
"""
|
||||
List of directories that were created.
|
||||
"""
|
||||
return self._dirs_created
|
||||
|
||||
|
||||
class DirectorySnapshot:
|
||||
"""
|
||||
A snapshot of stat information of files in a directory.
|
||||
|
||||
:param path:
|
||||
The directory path for which a snapshot should be taken.
|
||||
:type path:
|
||||
``str``
|
||||
:param recursive:
|
||||
``True`` if the entire directory tree should be included in the
|
||||
snapshot; ``False`` otherwise.
|
||||
:type recursive:
|
||||
``bool``
|
||||
:param stat:
|
||||
Use custom stat function that returns a stat structure for path.
|
||||
Currently only st_dev, st_ino, st_mode and st_mtime are needed.
|
||||
|
||||
A function taking a ``path`` as argument which will be called
|
||||
for every entry in the directory tree.
|
||||
:param listdir:
|
||||
Use custom listdir function. For details see ``os.scandir``.
|
||||
"""
|
||||
|
||||
def __init__(self, path, recursive=True,
|
||||
stat=os.stat, listdir=os.scandir):
|
||||
self.recursive = recursive
|
||||
self.stat = stat
|
||||
self.listdir = listdir
|
||||
|
||||
self._stat_info = {}
|
||||
self._inode_to_path = {}
|
||||
|
||||
st = self.stat(path)
|
||||
self._stat_info[path] = st
|
||||
self._inode_to_path[(st.st_ino, st.st_dev)] = path
|
||||
|
||||
for p, st in self.walk(path):
|
||||
i = (st.st_ino, st.st_dev)
|
||||
self._inode_to_path[i] = p
|
||||
self._stat_info[p] = st
|
||||
|
||||
def walk(self, root):
|
||||
try:
|
||||
paths = [os.path.join(root, entry if isinstance(entry, str) else entry.name)
|
||||
for entry in self.listdir(root)]
|
||||
except OSError as e:
|
||||
# Directory may have been deleted between finding it in the directory
|
||||
# list of its parent and trying to delete its contents. If this
|
||||
# happens we treat it as empty. Likewise if the directory was replaced
|
||||
# with a file of the same name (less likely, but possible).
|
||||
if e.errno in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
|
||||
return
|
||||
else:
|
||||
raise
|
||||
|
||||
entries = []
|
||||
for p in paths:
|
||||
try:
|
||||
entry = (p, self.stat(p))
|
||||
entries.append(entry)
|
||||
yield entry
|
||||
except OSError:
|
||||
continue
|
||||
|
||||
if self.recursive:
|
||||
for path, st in entries:
|
||||
try:
|
||||
if S_ISDIR(st.st_mode):
|
||||
for entry in self.walk(path):
|
||||
yield entry
|
||||
except PermissionError:
|
||||
pass
|
||||
|
||||
@property
|
||||
def paths(self):
|
||||
"""
|
||||
Set of file/directory paths in the snapshot.
|
||||
"""
|
||||
return set(self._stat_info.keys())
|
||||
|
||||
def path(self, id):
|
||||
"""
|
||||
Returns path for id. None if id is unknown to this snapshot.
|
||||
"""
|
||||
return self._inode_to_path.get(id)
|
||||
|
||||
def inode(self, path):
|
||||
""" Returns an id for path. """
|
||||
st = self._stat_info[path]
|
||||
return (st.st_ino, st.st_dev)
|
||||
|
||||
def isdir(self, path):
|
||||
return S_ISDIR(self._stat_info[path].st_mode)
|
||||
|
||||
def mtime(self, path):
|
||||
return self._stat_info[path].st_mtime
|
||||
|
||||
def size(self, path):
|
||||
return self._stat_info[path].st_size
|
||||
|
||||
def stat_info(self, path):
|
||||
"""
|
||||
Returns a stat information object for the specified path from
|
||||
the snapshot.
|
||||
|
||||
Attached information is subject to change. Do not use unless
|
||||
you specify `stat` in constructor. Use :func:`inode`, :func:`mtime`,
|
||||
:func:`isdir` instead.
|
||||
|
||||
:param path:
|
||||
The path for which stat information should be obtained
|
||||
from a snapshot.
|
||||
"""
|
||||
return self._stat_info[path]
|
||||
|
||||
def __sub__(self, previous_dirsnap):
|
||||
"""Allow subtracting a DirectorySnapshot object instance from
|
||||
another.
|
||||
|
||||
:returns:
|
||||
A :class:`DirectorySnapshotDiff` object.
|
||||
"""
|
||||
return DirectorySnapshotDiff(previous_dirsnap, self)
|
||||
|
||||
def __str__(self):
|
||||
return self.__repr__()
|
||||
|
||||
def __repr__(self):
|
||||
return str(self._stat_info)
|
||||
|
||||
|
||||
class EmptyDirectorySnapshot:
|
||||
"""Class to implement an empty snapshot. This is used together with
|
||||
DirectorySnapshot and DirectorySnapshotDiff in order to get all the files/folders
|
||||
in the directory as created.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def path(_):
|
||||
"""Mock up method to return the path of the received inode. As the snapshot
|
||||
is intended to be empty, it always returns None.
|
||||
|
||||
:returns:
|
||||
None.
|
||||
"""
|
||||
return None
|
||||
|
||||
@property
|
||||
def paths(self):
|
||||
"""Mock up method to return a set of file/directory paths in the snapshot. As
|
||||
the snapshot is intended to be empty, it always returns an empty set.
|
||||
|
||||
:returns:
|
||||
An empty set.
|
||||
"""
|
||||
return set()
|
||||
161
venv/lib/python3.11/site-packages/watchdog/utils/echo.py
Normal file
161
venv/lib/python3.11/site-packages/watchdog/utils/echo.py
Normal file
@ -0,0 +1,161 @@
|
||||
# coding: utf-8
|
||||
# echo.py: Tracing function calls using Python decorators.
|
||||
#
|
||||
# Written by Thomas Guest <tag@wordaligned.org>
|
||||
# Please see http://wordaligned.org/articles/echo
|
||||
#
|
||||
# Place into the public domain.
|
||||
|
||||
""" Echo calls made to functions and methods in a module.
|
||||
|
||||
"Echoing" a function call means printing out the name of the function
|
||||
and the values of its arguments before making the call (which is more
|
||||
commonly referred to as "tracing", but Python already has a trace module).
|
||||
|
||||
Example: to echo calls made to functions in "my_module" do:
|
||||
|
||||
import echo
|
||||
import my_module
|
||||
echo.echo_module(my_module)
|
||||
|
||||
Example: to echo calls made to functions in "my_module.my_class" do:
|
||||
|
||||
echo.echo_class(my_module.my_class)
|
||||
|
||||
Alternatively, echo.echo can be used to decorate functions. Calls to the
|
||||
decorated function will be echoed.
|
||||
|
||||
Example:
|
||||
|
||||
@echo.echo
|
||||
def my_function(args):
|
||||
pass
|
||||
"""
|
||||
import inspect
|
||||
import sys
|
||||
|
||||
|
||||
def name(item):
|
||||
" Return an item's name. "
|
||||
return item.__name__
|
||||
|
||||
|
||||
def is_classmethod(instancemethod, klass):
|
||||
" Determine if an instancemethod is a classmethod. "
|
||||
return inspect.ismethod(instancemethod) and instancemethod.__self__ is klass
|
||||
|
||||
|
||||
def is_static_method(method, klass):
|
||||
"""Returns True if method is an instance method of klass."""
|
||||
for c in klass.mro():
|
||||
if name(method) in c.__dict__:
|
||||
return isinstance(c.__dict__[name(method)], staticmethod)
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def is_class_private_name(name):
|
||||
" Determine if a name is a class private name. "
|
||||
# Exclude system defined names such as __init__, __add__ etc
|
||||
return name.startswith("__") and not name.endswith("__")
|
||||
|
||||
|
||||
def method_name(method):
|
||||
""" Return a method's name.
|
||||
|
||||
This function returns the name the method is accessed by from
|
||||
outside the class (i.e. it prefixes "private" methods appropriately).
|
||||
"""
|
||||
mname = name(method)
|
||||
if is_class_private_name(mname):
|
||||
mname = "_%s%s" % (name(method.__self__.__class__), mname)
|
||||
return mname
|
||||
|
||||
|
||||
def format_arg_value(arg_val):
|
||||
""" Return a string representing a (name, value) pair.
|
||||
|
||||
>>> format_arg_value(('x', (1, 2, 3)))
|
||||
'x=(1, 2, 3)'
|
||||
"""
|
||||
arg, val = arg_val
|
||||
return "%s=%r" % (arg, val)
|
||||
|
||||
|
||||
def echo(fn, write=sys.stdout.write):
|
||||
""" Echo calls to a function.
|
||||
|
||||
Returns a decorated version of the input function which "echoes" calls
|
||||
made to it by writing out the function's name and the arguments it was
|
||||
called with.
|
||||
"""
|
||||
import functools
|
||||
# Unpack function's arg count, arg names, arg defaults
|
||||
code = fn.__code__
|
||||
argcount = code.co_argcount
|
||||
argnames = code.co_varnames[:argcount]
|
||||
fn_defaults = fn.__defaults__ or list()
|
||||
argdefs = dict(list(zip(argnames[-len(fn_defaults):], fn_defaults)))
|
||||
|
||||
@functools.wraps(fn)
|
||||
def wrapped(*v, **k):
|
||||
# Collect function arguments by chaining together positional,
|
||||
# defaulted, extra positional and keyword arguments.
|
||||
positional = list(map(format_arg_value, list(zip(argnames, v))))
|
||||
defaulted = [format_arg_value((a, argdefs[a]))
|
||||
for a in argnames[len(v):] if a not in k]
|
||||
nameless = list(map(repr, v[argcount:]))
|
||||
keyword = list(map(format_arg_value, list(k.items())))
|
||||
args = positional + defaulted + nameless + keyword
|
||||
write("%s(%s)\n" % (name(fn), ", ".join(args)))
|
||||
return fn(*v, **k)
|
||||
|
||||
return wrapped
|
||||
|
||||
|
||||
def echo_instancemethod(klass, method, write=sys.stdout.write):
|
||||
""" Change an instancemethod so that calls to it are echoed.
|
||||
|
||||
Replacing a classmethod is a little more tricky.
|
||||
See: http://www.python.org/doc/current/ref/types.html
|
||||
"""
|
||||
mname = method_name(method)
|
||||
never_echo = "__str__", "__repr__", # Avoid recursion printing method calls
|
||||
if mname in never_echo:
|
||||
pass
|
||||
elif is_classmethod(method, klass):
|
||||
setattr(klass, mname, classmethod(echo(method.__func__, write)))
|
||||
else:
|
||||
setattr(klass, mname, echo(method, write))
|
||||
|
||||
|
||||
def echo_class(klass, write=sys.stdout.write):
|
||||
""" Echo calls to class methods and static functions
|
||||
"""
|
||||
for _, method in inspect.getmembers(klass, inspect.ismethod):
|
||||
# In python 3 only class methods are returned here, but in python2 instance methods are too.
|
||||
echo_instancemethod(klass, method, write)
|
||||
for _, fn in inspect.getmembers(klass, inspect.isfunction):
|
||||
if is_static_method(fn, klass):
|
||||
setattr(klass, name(fn), staticmethod(echo(fn, write)))
|
||||
else:
|
||||
# It's not a class or a static method, so it must be an instance method.
|
||||
# This should only be called in python 3, because in python 3 instance methods are considered functions.
|
||||
echo_instancemethod(klass, fn, write)
|
||||
|
||||
|
||||
def echo_module(mod, write=sys.stdout.write):
|
||||
""" Echo calls to functions and methods in a module.
|
||||
"""
|
||||
for fname, fn in inspect.getmembers(mod, inspect.isfunction):
|
||||
setattr(mod, fname, echo(fn, write))
|
||||
for _, klass in inspect.getmembers(mod, inspect.isclass):
|
||||
echo_class(klass, write)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
optionflags = doctest.ELLIPSIS
|
||||
doctest.testfile('echoexample.txt', optionflags=optionflags)
|
||||
doctest.testmod(optionflags=optionflags)
|
||||
87
venv/lib/python3.11/site-packages/watchdog/utils/patterns.py
Normal file
87
venv/lib/python3.11/site-packages/watchdog/utils/patterns.py
Normal file
@ -0,0 +1,87 @@
|
||||
# coding: utf-8
|
||||
# patterns.py: Common wildcard searching/filtering functionality for files.
|
||||
#
|
||||
# Copyright (C) 2010 Yesudeep Mangalapilly <yesudeep@gmail.com>
|
||||
#
|
||||
# Written by Boris Staletic <boris.staletic@gmail.com>
|
||||
|
||||
# Non-pure path objects are only allowed on their respective OS's.
|
||||
# Thus, these utilities require "pure" path objects that don't access the filesystem.
|
||||
# Since pathlib doesn't have a `case_sensitive` parameter, we have to approximate it
|
||||
# by converting input paths to `PureWindowsPath` and `PurePosixPath` where:
|
||||
# - `PureWindowsPath` is always case-insensitive.
|
||||
# - `PurePosixPath` is always case-sensitive.
|
||||
# Reference: https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.match
|
||||
from pathlib import PureWindowsPath, PurePosixPath
|
||||
|
||||
|
||||
def _match_path(path, included_patterns, excluded_patterns, case_sensitive):
|
||||
"""Internal function same as :func:`match_path` but does not check arguments."""
|
||||
if case_sensitive:
|
||||
path = PurePosixPath(path)
|
||||
else:
|
||||
included_patterns = {pattern.lower() for pattern in included_patterns}
|
||||
excluded_patterns = {pattern.lower() for pattern in excluded_patterns}
|
||||
path = PureWindowsPath(path)
|
||||
|
||||
common_patterns = included_patterns & excluded_patterns
|
||||
if common_patterns:
|
||||
raise ValueError('conflicting patterns `{}` included and excluded'.format(common_patterns))
|
||||
return (any(path.match(p) for p in included_patterns)
|
||||
and not any(path.match(p) for p in excluded_patterns))
|
||||
|
||||
|
||||
def filter_paths(paths, included_patterns=None, excluded_patterns=None, case_sensitive=True):
|
||||
"""
|
||||
Filters from a set of paths based on acceptable patterns and
|
||||
ignorable patterns.
|
||||
:param pathnames:
|
||||
A list of path names that will be filtered based on matching and
|
||||
ignored patterns.
|
||||
:param included_patterns:
|
||||
Allow filenames matching wildcard patterns specified in this list.
|
||||
If no pattern list is specified, ["*"] is used as the default pattern,
|
||||
which matches all files.
|
||||
:param excluded_patterns:
|
||||
Ignores filenames matching wildcard patterns specified in this list.
|
||||
If no pattern list is specified, no files are ignored.
|
||||
:param case_sensitive:
|
||||
``True`` if matching should be case-sensitive; ``False`` otherwise.
|
||||
:returns:
|
||||
A list of pathnames that matched the allowable patterns and passed
|
||||
through the ignored patterns.
|
||||
"""
|
||||
included = ["*"] if included_patterns is None else included_patterns
|
||||
excluded = [] if excluded_patterns is None else excluded_patterns
|
||||
|
||||
for path in paths:
|
||||
if _match_path(path, set(included), set(excluded), case_sensitive):
|
||||
yield path
|
||||
|
||||
|
||||
def match_any_paths(paths, included_patterns=None, excluded_patterns=None, case_sensitive=True):
|
||||
"""
|
||||
Matches from a set of paths based on acceptable patterns and
|
||||
ignorable patterns.
|
||||
:param pathnames:
|
||||
A list of path names that will be filtered based on matching and
|
||||
ignored patterns.
|
||||
:param included_patterns:
|
||||
Allow filenames matching wildcard patterns specified in this list.
|
||||
If no pattern list is specified, ["*"] is used as the default pattern,
|
||||
which matches all files.
|
||||
:param excluded_patterns:
|
||||
Ignores filenames matching wildcard patterns specified in this list.
|
||||
If no pattern list is specified, no files are ignored.
|
||||
:param case_sensitive:
|
||||
``True`` if matching should be case-sensitive; ``False`` otherwise.
|
||||
:returns:
|
||||
``True`` if any of the paths matches; ``False`` otherwise.
|
||||
"""
|
||||
included = ["*"] if included_patterns is None else included_patterns
|
||||
excluded = [] if excluded_patterns is None else excluded_patterns
|
||||
|
||||
for path in paths:
|
||||
if _match_path(path, set(included), set(excluded), case_sensitive):
|
||||
return True
|
||||
return False
|
||||
57
venv/lib/python3.11/site-packages/watchdog/utils/platform.py
Normal file
57
venv/lib/python3.11/site-packages/watchdog/utils/platform.py
Normal file
@ -0,0 +1,57 @@
|
||||
# coding: utf-8
|
||||
#
|
||||
# Copyright 2011 Yesudeep Mangalapilly <yesudeep@gmail.com>
|
||||
# Copyright 2012 Google, Inc & contributors.
|
||||
#
|
||||
# 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.
|
||||
|
||||
|
||||
import sys
|
||||
|
||||
PLATFORM_WINDOWS = 'windows'
|
||||
PLATFORM_LINUX = 'linux'
|
||||
PLATFORM_BSD = 'bsd'
|
||||
PLATFORM_DARWIN = 'darwin'
|
||||
PLATFORM_UNKNOWN = 'unknown'
|
||||
|
||||
|
||||
def get_platform_name():
|
||||
if sys.platform.startswith("win"):
|
||||
return PLATFORM_WINDOWS
|
||||
elif sys.platform.startswith('darwin'):
|
||||
return PLATFORM_DARWIN
|
||||
elif sys.platform.startswith('linux'):
|
||||
return PLATFORM_LINUX
|
||||
elif sys.platform.startswith(('dragonfly', 'freebsd', 'netbsd', 'openbsd', 'bsd')):
|
||||
return PLATFORM_BSD
|
||||
else:
|
||||
return PLATFORM_UNKNOWN
|
||||
|
||||
|
||||
__platform__ = get_platform_name()
|
||||
|
||||
|
||||
def is_linux():
|
||||
return __platform__ == PLATFORM_LINUX
|
||||
|
||||
|
||||
def is_bsd():
|
||||
return __platform__ == PLATFORM_BSD
|
||||
|
||||
|
||||
def is_darwin():
|
||||
return __platform__ == PLATFORM_DARWIN
|
||||
|
||||
|
||||
def is_windows():
|
||||
return __platform__ == PLATFORM_WINDOWS
|
||||
@ -0,0 +1,25 @@
|
||||
import logging
|
||||
|
||||
from watchdog.utils import BaseThread
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ProcessWatcher(BaseThread):
|
||||
def __init__(self, popen_obj, process_termination_callback):
|
||||
super().__init__()
|
||||
self.popen_obj = popen_obj
|
||||
self.process_termination_callback = process_termination_callback
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
if self.popen_obj.poll() is not None:
|
||||
break
|
||||
if self.stopped_event.wait(timeout=0.1):
|
||||
return
|
||||
|
||||
try:
|
||||
self.process_termination_callback()
|
||||
except Exception:
|
||||
logger.exception("Error calling process termination callback")
|
||||
Reference in New Issue
Block a user