bitbake: bitbake/pyinotify.py: Upgrade to py3 version

(Bitbake rev: 5ee80d77bc278758e411048ed09551ab65b9e72d)

Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
This commit is contained in:
Richard Purdie 2016-05-06 09:07:37 +01:00
parent 822eabf32d
commit deca147645
1 changed files with 101 additions and 155 deletions

View File

@ -42,13 +42,14 @@ class UnsupportedPythonVersionError(PyinotifyError):
@param version: Current Python version @param version: Current Python version
@type version: string @type version: string
""" """
err = 'Python %s is unsupported, requires at least Python 2.4' PyinotifyError.__init__(self,
PyinotifyError.__init__(self, err % version) ('Python %s is unsupported, requires '
'at least Python 3.0') % version)
# Check Python version # Check Python version
import sys import sys
if sys.version_info < (2, 4): if sys.version_info < (3, 0):
raise UnsupportedPythonVersionError(sys.version) raise UnsupportedPythonVersionError(sys.version)
@ -68,6 +69,8 @@ from datetime import datetime, timedelta
import time import time
import re import re
import asyncore import asyncore
import glob
import locale
import subprocess import subprocess
try: try:
@ -75,12 +78,6 @@ try:
except ImportError: except ImportError:
pass # Will fail on Python 2.4 which has reduce() builtin anyway. pass # Will fail on Python 2.4 which has reduce() builtin anyway.
try:
from glob import iglob as glob
except ImportError:
# Python 2.4 does not have glob.iglob().
from glob import glob as glob
try: try:
import ctypes import ctypes
import ctypes.util import ctypes.util
@ -95,9 +92,7 @@ except ImportError:
__author__ = "seb@dbzteam.org (Sebastien Martini)" __author__ = "seb@dbzteam.org (Sebastien Martini)"
__version__ = "0.9.5" __version__ = "0.9.6"
__metaclass__ = type # Use new-style classes by default
# Compatibity mode: set to True to improve compatibility with # Compatibity mode: set to True to improve compatibility with
@ -122,6 +117,9 @@ class INotifyWrapper:
""" """
@staticmethod @staticmethod
def create(): def create():
"""
Factory method instanciating and returning the right wrapper.
"""
# First, try to use ctypes. # First, try to use ctypes.
if ctypes: if ctypes:
inotify = _CtypesLibcINotifyWrapper() inotify = _CtypesLibcINotifyWrapper()
@ -173,7 +171,7 @@ class _INotifySyscallsWrapper(INotifyWrapper):
def _inotify_init(self): def _inotify_init(self):
try: try:
fd = inotify_syscalls.inotify_init() fd = inotify_syscalls.inotify_init()
except IOError, err: except IOError as err:
self._last_errno = err.errno self._last_errno = err.errno
return -1 return -1
return fd return fd
@ -181,7 +179,7 @@ class _INotifySyscallsWrapper(INotifyWrapper):
def _inotify_add_watch(self, fd, pathname, mask): def _inotify_add_watch(self, fd, pathname, mask):
try: try:
wd = inotify_syscalls.inotify_add_watch(fd, pathname, mask) wd = inotify_syscalls.inotify_add_watch(fd, pathname, mask)
except IOError, err: except IOError as err:
self._last_errno = err.errno self._last_errno = err.errno
return -1 return -1
return wd return wd
@ -189,7 +187,7 @@ class _INotifySyscallsWrapper(INotifyWrapper):
def _inotify_rm_watch(self, fd, wd): def _inotify_rm_watch(self, fd, wd):
try: try:
ret = inotify_syscalls.inotify_rm_watch(fd, wd) ret = inotify_syscalls.inotify_rm_watch(fd, wd)
except IOError, err: except IOError as err:
self._last_errno = err.errno self._last_errno = err.errno
return -1 return -1
return ret return ret
@ -213,17 +211,8 @@ class _CtypesLibcINotifyWrapper(INotifyWrapper):
except (OSError, IOError): except (OSError, IOError):
pass # Will attemp to load it with None anyway. pass # Will attemp to load it with None anyway.
if sys.version_info >= (2, 6): self._libc = ctypes.CDLL(libc_name, use_errno=True)
self._libc = ctypes.CDLL(libc_name, use_errno=True) self._get_errno_func = ctypes.get_errno
self._get_errno_func = ctypes.get_errno
else:
self._libc = ctypes.CDLL(libc_name)
try:
location = self._libc.__errno_location
location.restype = ctypes.POINTER(ctypes.c_int)
self._get_errno_func = lambda: location().contents.value
except AttributeError:
pass
# Eventually check that libc has needed inotify bindings. # Eventually check that libc has needed inotify bindings.
if (not hasattr(self._libc, 'inotify_init') or if (not hasattr(self._libc, 'inotify_init') or
@ -241,9 +230,8 @@ class _CtypesLibcINotifyWrapper(INotifyWrapper):
return True return True
def _get_errno(self): def _get_errno(self):
if self._get_errno_func is not None: assert self._get_errno_func
return self._get_errno_func() return self._get_errno_func()
return None
def _inotify_init(self): def _inotify_init(self):
assert self._libc is not None assert self._libc is not None
@ -251,6 +239,11 @@ class _CtypesLibcINotifyWrapper(INotifyWrapper):
def _inotify_add_watch(self, fd, pathname, mask): def _inotify_add_watch(self, fd, pathname, mask):
assert self._libc is not None assert self._libc is not None
# Encodes path to a bytes string. This conversion seems required because
# ctypes.create_string_buffer seems to manipulate bytes internally.
# Moreover it seems that inotify_add_watch does not work very well when
# it receives an ctypes.create_unicode_buffer instance as argument.
pathname = pathname.encode(sys.getfilesystemencoding())
pathname = ctypes.create_string_buffer(pathname) pathname = ctypes.create_string_buffer(pathname)
return self._libc.inotify_add_watch(fd, pathname, mask) return self._libc.inotify_add_watch(fd, pathname, mask)
@ -258,10 +251,6 @@ class _CtypesLibcINotifyWrapper(INotifyWrapper):
assert self._libc is not None assert self._libc is not None
return self._libc.inotify_rm_watch(fd, wd) return self._libc.inotify_rm_watch(fd, wd)
def _sysctl(self, *args):
assert self._libc is not None
return self._libc.sysctl(*args)
# Logging # Logging
def logger_init(): def logger_init():
@ -278,97 +267,58 @@ log = logger_init()
# inotify's variables # inotify's variables
class SysCtlINotify: class ProcINotify:
""" """
Access (read, write) inotify's variables through sysctl. Usually it Access (read, write) inotify's variables through /proc/sys/. Note that
requires administrator rights to update them. usually it requires administrator rights to update them.
Examples: Examples:
- Read max_queued_events attribute: myvar = max_queued_events.value - Read max_queued_events attribute: myvar = max_queued_events.value
- Update max_queued_events attribute: max_queued_events.value = 42 - Update max_queued_events attribute: max_queued_events.value = 42
""" """
def __init__(self, attr):
inotify_attrs = {'max_user_instances': 1, self._base = "/proc/sys/fs/inotify"
'max_user_watches': 2, self._attr = attr
'max_queued_events': 3}
def __init__(self, attrname, inotify_wrapper):
# FIXME: right now only supporting ctypes
assert ctypes
self._attrname = attrname
self._inotify_wrapper = inotify_wrapper
sino = ctypes.c_int * 3
self._attr = sino(5, 20, SysCtlINotify.inotify_attrs[attrname])
@staticmethod
def create(attrname):
"""
Factory method instanciating and returning the right wrapper.
"""
# FIXME: right now only supporting ctypes
if ctypes is None:
return None
inotify_wrapper = _CtypesLibcINotifyWrapper()
if not inotify_wrapper.init():
return None
return SysCtlINotify(attrname, inotify_wrapper)
def get_val(self): def get_val(self):
""" """
Gets attribute's value. Raises OSError if the operation failed. Gets attribute's value.
@return: stored value. @return: stored value.
@rtype: int @rtype: int
@raise IOError: if corresponding file in /proc/sys cannot be read.
""" """
oldv = ctypes.c_int(0) with open(os.path.join(self._base, self._attr), 'r') as file_obj:
size = ctypes.c_int(ctypes.sizeof(oldv)) return int(file_obj.readline())
sysctl = self._inotify_wrapper._sysctl
res = sysctl(self._attr, 3,
ctypes.c_voidp(ctypes.addressof(oldv)),
ctypes.addressof(size),
None, 0)
if res == -1:
raise OSError(self._inotify_wrapper.get_errno(),
self._inotify_wrapper.str_errno())
return oldv.value
def set_val(self, nval): def set_val(self, nval):
""" """
Sets new attribute's value. Raises OSError if the operation failed. Sets new attribute's value.
@param nval: replaces current value by nval. @param nval: replaces current value by nval.
@type nval: int @type nval: int
@raise IOError: if corresponding file in /proc/sys cannot be written.
""" """
oldv = ctypes.c_int(0) with open(os.path.join(self._base, self._attr), 'w') as file_obj:
sizeo = ctypes.c_int(ctypes.sizeof(oldv)) file_obj.write(str(nval) + '\n')
newv = ctypes.c_int(nval)
sizen = ctypes.c_int(ctypes.sizeof(newv))
sysctl = self._inotify_wrapper._sysctl
res = sysctl(self._attr, 3,
ctypes.c_voidp(ctypes.addressof(oldv)),
ctypes.addressof(sizeo),
ctypes.c_voidp(ctypes.addressof(newv)),
sizen)
if res == -1:
raise OSError(self._inotify_wrapper.get_errno(),
self._inotify_wrapper.str_errno())
value = property(get_val, set_val) value = property(get_val, set_val)
def __repr__(self): def __repr__(self):
return '<%s=%d>' % (self._attrname, self.get_val()) return '<%s=%d>' % (self._attr, self.get_val())
# Inotify's variables # Inotify's variables
# #
# FIXME: currently these variables are only accessible when ctypes is used, # Note: may raise IOError if the corresponding value in /proc/sys
# otherwise there are set to None. # cannot be accessed.
# #
# read: myvar = max_queued_events.value # Examples:
# update: max_queued_events.value = 42 # - read: myvar = max_queued_events.value
# - update: max_queued_events.value = 42
# #
for attrname in ('max_queued_events', 'max_user_instances', 'max_user_watches'): for attrname in ('max_queued_events', 'max_user_instances', 'max_user_watches'):
globals()[attrname] = SysCtlINotify.create(attrname) globals()[attrname] = ProcINotify(attrname)
class EventsCodes: class EventsCodes:
@ -536,7 +486,7 @@ class _Event:
continue continue
if attr == 'mask': if attr == 'mask':
value = hex(getattr(self, attr)) value = hex(getattr(self, attr))
elif isinstance(value, basestring) and not value: elif isinstance(value, str) and not value:
value = "''" value = "''"
s += ' %s%s%s' % (output_format.field_name(attr), s += ' %s%s%s' % (output_format.field_name(attr),
output_format.punctuation('='), output_format.punctuation('='),
@ -628,7 +578,7 @@ class Event(_Event):
self.name)) self.name))
else: else:
self.pathname = os.path.abspath(self.path) self.pathname = os.path.abspath(self.path)
except AttributeError, err: except AttributeError as err:
# Usually it is not an error some events are perfectly valids # Usually it is not an error some events are perfectly valids
# despite the lack of these attributes. # despite the lack of these attributes.
log.debug(err) log.debug(err)
@ -718,8 +668,8 @@ class _SysProcessEvent(_ProcessEvent):
and self._mv. and self._mv.
""" """
date_cur_ = datetime.now() date_cur_ = datetime.now()
for seq in [self._mv_cookie, self._mv]: for seq in (self._mv_cookie, self._mv):
for k in seq.keys(): for k in list(seq.keys()):
if (date_cur_ - seq[k][1]) > timedelta(minutes=1): if (date_cur_ - seq[k][1]) > timedelta(minutes=1):
log.debug('Cleanup: deleting entry %s', seq[k][0]) log.debug('Cleanup: deleting entry %s', seq[k][0])
del seq[k] del seq[k]
@ -767,9 +717,9 @@ class _SysProcessEvent(_ProcessEvent):
continue continue
rawevent = _RawEvent(created_dir_wd, flags, 0, name) rawevent = _RawEvent(created_dir_wd, flags, 0, name)
self._notifier.append_event(rawevent) self._notifier.append_event(rawevent)
except OSError, err: except OSError as err:
msg = "process_IN_CREATE, invalid directory %s: %s" msg = "process_IN_CREATE, invalid directory: %s"
log.debug(msg % (created_dir, str(err))) log.debug(msg % str(err))
return self.process_default(raw_event) return self.process_default(raw_event)
def process_IN_MOVED_FROM(self, raw_event): def process_IN_MOVED_FROM(self, raw_event):
@ -1097,8 +1047,8 @@ class Stats(ProcessEvent):
@type filename: string @type filename: string
""" """
flags = os.O_WRONLY|os.O_CREAT|os.O_NOFOLLOW|os.O_EXCL flags = os.O_WRONLY|os.O_CREAT|os.O_NOFOLLOW|os.O_EXCL
fd = os.open(filename, flags, 0600) fd = os.open(filename, flags, 0o0600)
os.write(fd, str(self)) os.write(fd, bytes(self.__str__(), locale.getpreferredencoding()))
os.close(fd) os.close(fd)
def __str__(self, scale=45): def __str__(self, scale=45):
@ -1107,7 +1057,7 @@ class Stats(ProcessEvent):
return '' return ''
m = max(stats.values()) m = max(stats.values())
unity = float(scale) / m unity = scale / m
fmt = '%%-26s%%-%ds%%s' % (len(output_format.field_value('@' * scale)) fmt = '%%-26s%%-%ds%%s' % (len(output_format.field_value('@' * scale))
+ 1) + 1)
def func(x): def func(x):
@ -1149,7 +1099,7 @@ class Notifier:
@type default_proc_fun: instance of ProcessEvent @type default_proc_fun: instance of ProcessEvent
@param read_freq: if read_freq == 0, events are read asap, @param read_freq: if read_freq == 0, events are read asap,
if read_freq is > 0, this thread sleeps if read_freq is > 0, this thread sleeps
max(0, read_freq - timeout) seconds. But if max(0, read_freq - (timeout / 1000)) seconds. But if
timeout is None it may be different because timeout is None it may be different because
poll is blocking waiting for something to read. poll is blocking waiting for something to read.
@type read_freq: int @type read_freq: int
@ -1161,8 +1111,9 @@ class Notifier:
until the amount of events to read is >= threshold. until the amount of events to read is >= threshold.
At least with read_freq set you might sleep. At least with read_freq set you might sleep.
@type threshold: int @type threshold: int
@param timeout: @param timeout: see read_freq above. If provided, it must be set in
https://docs.python.org/3/library/select.html#polling-objects milliseconds. See
https://docs.python.org/3/library/select.html#select.poll.poll
@type timeout: int @type timeout: int
""" """
# Watch Manager instance # Watch Manager instance
@ -1228,7 +1179,8 @@ class Notifier:
milliseconds. milliseconds.
@param timeout: If specified it overrides the corresponding instance @param timeout: If specified it overrides the corresponding instance
attribute _timeout. attribute _timeout. timeout must be sepcified in
milliseconds.
@type timeout: int @type timeout: int
@return: New events to read. @return: New events to read.
@ -1240,8 +1192,8 @@ class Notifier:
if timeout is None: if timeout is None:
timeout = self._timeout timeout = self._timeout
ret = self._pollobj.poll(timeout) ret = self._pollobj.poll(timeout)
except select.error, err: except select.error as err:
if err[0] == errno.EINTR: if err.args[0] == errno.EINTR:
continue # interrupted, retry continue # interrupted, retry
else: else:
raise raise
@ -1271,7 +1223,7 @@ class Notifier:
try: try:
# Read content from file # Read content from file
r = os.read(self._fd, queue_size) r = os.read(self._fd, queue_size)
except Exception, msg: except Exception as msg:
raise NotifierError(msg) raise NotifierError(msg)
log.debug('Event queue size: %d', queue_size) log.debug('Event queue size: %d', queue_size)
rsum = 0 # counter rsum = 0 # counter
@ -1281,9 +1233,11 @@ class Notifier:
wd, mask, cookie, fname_len = struct.unpack('iIII', wd, mask, cookie, fname_len = struct.unpack('iIII',
r[rsum:rsum+s_size]) r[rsum:rsum+s_size])
# Retrieve name # Retrieve name
fname, = struct.unpack('%ds' % fname_len, bname, = struct.unpack('%ds' % fname_len,
r[rsum + s_size:rsum + s_size + fname_len]) r[rsum + s_size:rsum + s_size + fname_len])
rawevent = _RawEvent(wd, mask, cookie, fname) # FIXME: should we explictly call sys.getdefaultencoding() here ??
uname = bname.decode()
rawevent = _RawEvent(wd, mask, cookie, uname)
if self._coalesce: if self._coalesce:
# Only enqueue new (unique) events. # Only enqueue new (unique) events.
raweventstr = str(rawevent) raweventstr = str(rawevent)
@ -1326,13 +1280,10 @@ class Notifier:
def __daemonize(self, pid_file=None, stdin=os.devnull, stdout=os.devnull, def __daemonize(self, pid_file=None, stdin=os.devnull, stdout=os.devnull,
stderr=os.devnull): stderr=os.devnull):
""" """
@param pid_file: file where the pid will be written. If pid_file=None pid_file: file where the pid will be written. If pid_file=None the pid
the pid is written to is written to /var/run/<sys.argv[0]|pyinotify>.pid, if
/var/run/<sys.argv[0]|pyinotify>.pid, if pid_file=False pid_file=False no pid_file is written.
no pid_file is written. stdin, stdout, stderr: files associated to common streams.
@param stdin:
@param stdout:
@param stderr: files associated to common streams.
""" """
if pid_file is None: if pid_file is None:
dirname = '/var/run/' dirname = '/var/run/'
@ -1354,7 +1305,7 @@ class Notifier:
if (pid == 0): if (pid == 0):
# child # child
os.chdir('/') os.chdir('/')
os.umask(022) os.umask(0o022)
else: else:
# parent 2 # parent 2
os._exit(0) os._exit(0)
@ -1364,9 +1315,9 @@ class Notifier:
fd_inp = os.open(stdin, os.O_RDONLY) fd_inp = os.open(stdin, os.O_RDONLY)
os.dup2(fd_inp, 0) os.dup2(fd_inp, 0)
fd_out = os.open(stdout, os.O_WRONLY|os.O_CREAT, 0600) fd_out = os.open(stdout, os.O_WRONLY|os.O_CREAT, 0o0600)
os.dup2(fd_out, 1) os.dup2(fd_out, 1)
fd_err = os.open(stderr, os.O_WRONLY|os.O_CREAT, 0600) fd_err = os.open(stderr, os.O_WRONLY|os.O_CREAT, 0o0600)
os.dup2(fd_err, 2) os.dup2(fd_err, 2)
# Detach task # Detach task
@ -1375,8 +1326,9 @@ class Notifier:
# Write pid # Write pid
if pid_file != False: if pid_file != False:
flags = os.O_WRONLY|os.O_CREAT|os.O_NOFOLLOW|os.O_EXCL flags = os.O_WRONLY|os.O_CREAT|os.O_NOFOLLOW|os.O_EXCL
fd_pid = os.open(pid_file, flags, 0600) fd_pid = os.open(pid_file, flags, 0o0600)
os.write(fd_pid, str(os.getpid()) + '\n') os.write(fd_pid, bytes(str(os.getpid()) + '\n',
locale.getpreferredencoding()))
os.close(fd_pid) os.close(fd_pid)
# Register unlink function # Register unlink function
atexit.register(lambda : os.unlink(pid_file)) atexit.register(lambda : os.unlink(pid_file))
@ -1441,9 +1393,12 @@ class Notifier:
Close inotify's instance (close its file descriptor). Close inotify's instance (close its file descriptor).
It destroys all existing watches, pending events,... It destroys all existing watches, pending events,...
This method is automatically called at the end of loop(). This method is automatically called at the end of loop().
Afterward it is invalid to access this instance.
""" """
self._pollobj.unregister(self._fd) if self._fd is not None:
os.close(self._fd) self._pollobj.unregister(self._fd)
os.close(self._fd)
self._fd = None
self._sys_proc_fun = None self._sys_proc_fun = None
@ -1468,7 +1423,7 @@ class ThreadedNotifier(threading.Thread, Notifier):
@type default_proc_fun: instance of ProcessEvent @type default_proc_fun: instance of ProcessEvent
@param read_freq: if read_freq == 0, events are read asap, @param read_freq: if read_freq == 0, events are read asap,
if read_freq is > 0, this thread sleeps if read_freq is > 0, this thread sleeps
max(0, read_freq - timeout) seconds. max(0, read_freq - (timeout / 1000)) seconds.
@type read_freq: int @type read_freq: int
@param threshold: File descriptor will be read only if the accumulated @param threshold: File descriptor will be read only if the accumulated
size to read becomes >= threshold. If != 0, you likely size to read becomes >= threshold. If != 0, you likely
@ -1478,8 +1433,9 @@ class ThreadedNotifier(threading.Thread, Notifier):
until the amount of events to read is >= threshold. At until the amount of events to read is >= threshold. At
least with read_freq you might sleep. least with read_freq you might sleep.
@type threshold: int @type threshold: int
@param timeout: @param timeout: see read_freq above. If provided, it must be set in
https://docs.python.org/3/library/select.html#polling-objects milliseconds. See
https://docs.python.org/3/library/select.html#select.poll.poll
@type timeout: int @type timeout: int
""" """
# Init threading base class # Init threading base class
@ -1498,7 +1454,7 @@ class ThreadedNotifier(threading.Thread, Notifier):
Stop notifier's loop. Stop notification. Join the thread. Stop notifier's loop. Stop notification. Join the thread.
""" """
self._stop_event.set() self._stop_event.set()
os.write(self._pipe[1], 'stop') os.write(self._pipe[1], b'stop')
threading.Thread.join(self) threading.Thread.join(self)
Notifier.stop(self) Notifier.stop(self)
self._pollobj.unregister(self._pipe[0]) self._pollobj.unregister(self._pipe[0])
@ -1699,7 +1655,6 @@ class Watch:
class ExcludeFilter: class ExcludeFilter:
""" """
ExcludeFilter is an exclusion filter. ExcludeFilter is an exclusion filter.
""" """
def __init__(self, arg_lst): def __init__(self, arg_lst):
""" """
@ -1731,16 +1686,13 @@ class ExcludeFilter:
def _load_patterns_from_file(self, filename): def _load_patterns_from_file(self, filename):
lst = [] lst = []
file_obj = file(filename, 'r') with open(filename, 'r') as file_obj:
try:
for line in file_obj.readlines(): for line in file_obj.readlines():
# Trim leading an trailing whitespaces # Trim leading an trailing whitespaces
pattern = line.strip() pattern = line.strip()
if not pattern or pattern.startswith('#'): if not pattern or pattern.startswith('#'):
continue continue
lst.append(pattern) lst.append(pattern)
finally:
file_obj.close()
return lst return lst
def _match(self, regex, path): def _match(self, regex, path):
@ -1764,7 +1716,6 @@ class WatchManagerError(Exception):
""" """
WatchManager Exception. Raised on error encountered on watches WatchManager Exception. Raised on error encountered on watches
operations. operations.
""" """
def __init__(self, msg, wmd): def __init__(self, msg, wmd):
""" """
@ -1851,7 +1802,7 @@ class WatchManager:
""" """
try: try:
del self._wmd[wd] del self._wmd[wd]
except KeyError, err: except KeyError as err:
log.error('Cannot delete unknown watch descriptor %s' % str(err)) log.error('Cannot delete unknown watch descriptor %s' % str(err))
@property @property
@ -1868,13 +1819,7 @@ class WatchManager:
""" """
Format path to its internal (stored in watch manager) representation. Format path to its internal (stored in watch manager) representation.
""" """
# Unicode strings are converted back to strings, because it seems # path must be a unicode string (str) and is just normalized.
# that inotify_add_watch from ctypes does not work well when
# it receives an ctypes.create_unicode_buffer instance as argument.
# Therefore even wd are indexed with bytes string and not with
# unicode paths.
if isinstance(path, unicode):
path = path.encode(sys.getfilesystemencoding())
return os.path.normpath(path) return os.path.normpath(path)
def __add_watch(self, path, mask, proc_fun, auto_add, exclude_filter): def __add_watch(self, path, mask, proc_fun, auto_add, exclude_filter):
@ -1890,13 +1835,14 @@ class WatchManager:
return wd return wd
watch = Watch(wd=wd, path=path, mask=mask, proc_fun=proc_fun, watch = Watch(wd=wd, path=path, mask=mask, proc_fun=proc_fun,
auto_add=auto_add, exclude_filter=exclude_filter) auto_add=auto_add, exclude_filter=exclude_filter)
# wd are _always_ indexed with their original unicode paths in wmd.
self._wmd[wd] = watch self._wmd[wd] = watch
log.debug('New %s', watch) log.debug('New %s', watch)
return wd return wd
def __glob(self, path, do_glob): def __glob(self, path, do_glob):
if do_glob: if do_glob:
return glob(path) return glob.iglob(path)
else: else:
return [path] return [path]
@ -1907,11 +1853,8 @@ class WatchManager:
Add watch(s) on the provided |path|(s) with associated |mask| flag Add watch(s) on the provided |path|(s) with associated |mask| flag
value and optionally with a processing |proc_fun| function and value and optionally with a processing |proc_fun| function and
recursive flag |rec| set to True. recursive flag |rec| set to True.
Ideally |path| components should not be unicode objects. Note that All |path| components _must_ be str (i.e. unicode) objects.
although unicode paths are accepted there are converted to byte If |path| is already watched it is ignored, but if it is called with
strings before a watch is put on that path. The encoding used for
converting the unicode object is given by sys.getfilesystemencoding().
If |path| si already watched it is ignored, but if it is called with
option rec=True a watch is put on each one of its not-watched option rec=True a watch is put on each one of its not-watched
subdirectory. subdirectory.
@ -1945,10 +1888,9 @@ class WatchManager:
the class' constructor. the class' constructor.
@type exclude_filter: callable object @type exclude_filter: callable object
@return: dict of paths associated to watch descriptors. A wd value @return: dict of paths associated to watch descriptors. A wd value
is positive if the watch was added sucessfully, is positive if the watch was added sucessfully, otherwise
otherwise the value is negative. If the path was invalid the value is negative. If the path was invalid or was already
or was already watched it is not included into this returned watched it is not included into this returned dictionary.
dictionary.
@rtype: dict of {str: int} @rtype: dict of {str: int}
""" """
ret_ = {} # return {path: wd, ...} ret_ = {} # return {path: wd, ...}
@ -1958,6 +1900,11 @@ class WatchManager:
# normalize args as list elements # normalize args as list elements
for npath in self.__format_param(path): for npath in self.__format_param(path):
# Require that path be a unicode string
if not isinstance(npath, str):
ret_[path] = -3
continue
# unix pathname pattern expansion # unix pathname pattern expansion
for apath in self.__glob(npath, do_glob): for apath in self.__glob(npath, do_glob):
# recursively list subdirs according to rec param # recursively list subdirs according to rec param
@ -2242,7 +2189,6 @@ class WatchManager:
"Make watch manager ignoring new events.") "Make watch manager ignoring new events.")
class RawOutputFormat: class RawOutputFormat:
""" """
Format string representations. Format string representations.