SF.net SVN: fclient: [13] trunk/fclient/fclient_lib
Status: Pre-Alpha
Brought to you by:
jurner
From: <jU...@us...> - 2007-10-27 16:57:37
|
Revision: 13 http://fclient.svn.sourceforge.net/fclient/?rev=13&view=rev Author: jUrner Date: 2007-10-27 09:57:28 -0700 (Sat, 27 Oct 2007) Log Message: ----------- added python extensions module Added Paths: ----------- trunk/fclient/fclient_lib/pyex/ trunk/fclient/fclient_lib/pyex/__init__.py trunk/fclient/fclient_lib/pyex/__init__.pyc trunk/fclient/fclient_lib/pyex/events.py trunk/fclient/fclient_lib/pyex/events.pyc trunk/fclient/fclient_lib/pyex/namespace.py trunk/fclient/fclient_lib/pyex/numbers.py trunk/fclient/fclient_lib/pyex/numbers.pyc trunk/fclient/fclient_lib/pyex/omapping.py Added: trunk/fclient/fclient_lib/pyex/__init__.py =================================================================== --- trunk/fclient/fclient_lib/pyex/__init__.py (rev 0) +++ trunk/fclient/fclient_lib/pyex/__init__.py 2007-10-27 16:57:28 UTC (rev 13) @@ -0,0 +1 @@ +"""Pytrhon extension modules""" Added: trunk/fclient/fclient_lib/pyex/__init__.pyc =================================================================== (Binary files differ) Property changes on: trunk/fclient/fclient_lib/pyex/__init__.pyc ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/fclient/fclient_lib/pyex/events.py =================================================================== --- trunk/fclient/fclient_lib/pyex/events.py (rev 0) +++ trunk/fclient/fclient_lib/pyex/events.py 2007-10-27 16:57:28 UTC (rev 13) @@ -0,0 +1,125 @@ +"""Signals and events""" + +#*********************************************************************** +# +# event handler. Mostly taken from +#http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/410686 +# +#*********************************************************************** +class EventMeta(type): + """Metaclass for events""" + + class Event(object): + """Event handler""" + + def __init__(self, name): + """ + @param name: name of the event + @attr name: name of the event + @attr observers: list of observers of the event + """ + self.name = name + self.observers = [] + + def __call__(self, *args, **kwargs): + """Dispatches the event and additional parameters to all observers registerd""" + for o in self.observers: + o(self, *args, **kwargs) + + def __iadd__(self, observer): + """Adds an observer to the event + @note: the observer will be called with the event as first paraeter, + followed by any number of *args or **kwargs passed by the caller of an event + """ + self.observers.append(observer) + return self + + def __isub__(self, observer): + """Removes the first occurence of an observer from the event""" + self.observers.remove(observer) + return self + + def __new__(clss, name, bases, kws): + events = kws.get('_events_', None) + if events is None: + raise ValueError('Event classes must implement an "_event_" attribute') + for event_name in events: + kws[event_name] = clss.Event(event_name) + return type.__new__(clss, name, bases, kws) + + +class Events(object): + """Base class for events + + Derrived classes should list events they support in the "_events_" tuple. + Each event name is automagically set as attribute of the event + class. + + Listeners may register to receiving events by calling __iadd__, + unregister by calling __isub__ on these attributes. Callback are + always called with the event as first argument, followed by additional + arguments, depending on the event. + + Events have the following methods: + + 'name': name of the event + 'observers': list of observers listening to the event + + + Note: + Always make shure to disconnnect when event notification is no longer desired + + + >>> class MyEvents(Events): + ... _events_ = ('FooEvent', 'BarEvent') + ... + >>> events = MyEvents() + >>> def cb(event): + ... print 'Received: %s' % event.name + + >>> events.FooEvent += cb + >>> events.FooEvent() + Received: FooEvent + + >>> events.FooEvent -= cb + >>> events.FooEvent() + + >>> events += ( (events.FooEvent, cb), (events.BarEvent, cb) ) + >>> events.FooEvent() + Received: FooEvent + >>> events.BarEvent() + Received: BarEvent + + >>> events -= ( (events.FooEvent, cb), (events.BarEvent, cb) ) + >>> events.FooEvent() + + >>> events.BarEvent() + + """ + __metaclass__ = EventMeta + _events_ = () + + def __iadd__(self, events): + """Adds one or more events / observers at once + @param events: tuple( (event, observer), (event, observer), ...) + """ + for event, observer in events: + event += observer + return self + + def __isub__(self, events): + """Removes one or more events / observers at once + @param events: tuple( (event, observer), (event, observer), ...) + """ + for event, observer in events: + event -= observer + return self + + +#********************************************************************* +# +#********************************************************************* +if __name__ == '__main__': + import doctest + doctest.testmod() + Added: trunk/fclient/fclient_lib/pyex/events.pyc =================================================================== (Binary files differ) Property changes on: trunk/fclient/fclient_lib/pyex/events.pyc ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/fclient/fclient_lib/pyex/namespace.py =================================================================== --- trunk/fclient/fclient_lib/pyex/namespace.py (rev 0) +++ trunk/fclient/fclient_lib/pyex/namespace.py 2007-10-27 16:57:28 UTC (rev 13) @@ -0,0 +1,115 @@ +"""Namespace handling""" + +import os +import re +#********************************************************************* +# +#********************************************************************* +def unique_filename(fpath, prefix='', names=None): + """Creates a filepath with a unique (human readable) filename + @param: fpath: filepath to patch + @param prefix: prefix to add to the filename or '' + @param names: list of filenames to patch agaunst or None to check files in the directory + of filepath + + @return: a filepath with one of these 'MyFile (1).txt' patched filenames + + >>> names = ['foo.txt', 'foo (1).txt'] + >>> unique_filename('foo.txt', names=names) + 'foo (2).txt' + + >>> names = ['foo.txt', 'foo (2).txt'] + >>> unique_filename('foo.txt', names=names) + 'foo (1).txt' + + >>> names = ['foo.txt', ] + >>> result = unique_filename(os.path.join('myDir', 'foo.txt'), names=names) + >>> expected = os.path.join('myDir', 'foo (1).txt') + >>> result == expected + True + + >>> names = ['foo.txt', ] + >>> unique_filename('foo.txt', names=names, prefix="Copy Of") + 'Copy Of foo.txt' + + >>> names = ['foo.txt', 'Copy Of foo.txt'] + >>> unique_filename('foo.txt', names=names, prefix="Copy Of") + 'Copy Of foo (1).txt' + + """ + filename = os.path.basename(fpath) + dirname = os.path.dirname(fpath) + if not filename: + raise ValueError("No filename found: %s" % fpath) + + n = 0 + tmp_filename = filename + while True: + if names is not None: + if tmp_filename not in names: break + else: + if not os.path.exists(os.path.join(dirname, tmp_filename)): break + + n += 1 + tmp_filename, tmp_ext = os.path.splitext(filename) + if prefix: + if n == 1: + tmp_filename = '%s %s' % (prefix, tmp_filename) + else: + tmp_filename = '%s %s (%s)' % (prefix, tmp_filename, n -1) + else: + tmp_filename = '%s (%s)' % (tmp_filename, n) + tmp_filename = tmp_filename + tmp_ext + + return os.path.join(dirname, tmp_filename) + + +#********************************************************************* +# +#********************************************************************* +def unquote_uri(uri, slash='-'): + """Unquotes an uri so it can be used as a name in the filesystem + @param uri: (str) uri to unquote + @param slasch: (str) substitution string shlashes to use or None to keep slashes + @return: (str) uri + + >>> unquote_uri('foo/bar') + 'foo-bar' + >>> unquote_uri('foo/b%20ar') + 'foo-b ar' + >>> unquote_uri('foo/b%34ar') + 'foo-b%34ar' + >>> unquote_uri('foo/bar', slash='77') + 'foo77bar' + >>> unquote_uri('foo/bar', slash=None) + 'foo/bar' + + """ + + # impossible to handle all the dos and donts on oses. So do minimum + # to enshure user readability + if slash is not None: + uri = uri.replace('/', slash) + uri = uri.replace('%20', ' ') + uri = uri.replace('%28', '(') + uri = uri.replace('%29', ')') + return uri + + +#********************************************************************* +# +#********************************************************************* +if __name__ == '__main__': + import doctest + doctest.testmod() + +#TODO: handle fpath='myFolder/foo (1).txt' ? +#a = 'foo (123)' +#p = re.compile('\A(.*)\(([0-9]+)\)\Z') + + + + + + + Added: trunk/fclient/fclient_lib/pyex/numbers.py =================================================================== --- trunk/fclient/fclient_lib/pyex/numbers.py (rev 0) +++ trunk/fclient/fclient_lib/pyex/numbers.py 2007-10-27 16:57:28 UTC (rev 13) @@ -0,0 +1,143 @@ +"""Number crunching and others + +""" + +#*************************************************************** +# +#*************************************************************** +def format_num_bytes(num, short=True, conform=True): + """Formats a number representing a number of bytes to a human readable string + @param num: (int) number to fomat + @param short: use short names + @param conform: if True factor is 1000, else factor is 1024 + @return: (str) formated number + + >>> format_num_bytes(100) + '100 b' + + >>> format_num_bytes(1000) + '1.00 kb' + + >>> format_num_bytes(1024, conform=False) + '1.00 kb' + + >>> format_num_bytes(1000, short=False) + '1.00 Kilobyte' + + """ + + if short: + names = ('b', 'kb', 'mb', 'gb', 'tb', 'pb', 'eb', 'zb', 'yb') + else: + names = ('Byte', + 'Kilobyte', + 'Megabyte', + 'Gigabyte', + 'Terabyte', + 'Petabyte', + 'Exabyte', + 'Zettabyte', + 'Yottabyte' + ) + if conform: + factor = 1000 + else: + factor = 1024 + + num = float(num) + name = names[0] + if num >= factor: + for tmp_name in names[1: ]: + num /= factor + name = tmp_name + if num < factor: + break + else: + return '%i %s' % (num, name) + + + + return '%01.2f %s' % (num, name) +#*************************************************************** +# +#*************************************************************** +TimeDurationNames = { + 'seconds': 's', + 'minutes': 'm', + 'hours': 'h', + 'days': 'd', + 'years': 'y' + } +def format_time_delta(t1, t2, names=None): + """Pretty prints a time delta + @arg t1: duration starting time + @arg t2: duration ending time + @arg names: (optional) dict mapping names to names as they should be + to be printed (see TimeDurationNames) + + >>> import time + >>> t0 = time.time() + + >>> format_time_delta(t0, t0 +1) + '1s' + + >>> format_time_delta(t0, t0) + '0s' + + >>> format_time_delta(t0, t0 +1.4) + '1.4s' + + >>> format_time_delta(t0, t0 +60) + '1m' + + >>> format_time_delta(t0, t0 +12345) + '3.4h' + + >>> format_time_delta(t0, t0 +1234567890) + '39.1y' + + >>> format_time_delta(t0, t0 +1234567890, names={'years': 'leapers', 'seconds': 's', 'minutes': 'm', 'hours': 'h', 'days': 'd'}) + '39.1leapers' + + """ + mapping = ( + ('years', 1), + ('days', 365), + ('hours', 24), + ('minutes', 60), + ('seconds', 60), + ) + if names is None: + names = TimeDurationNames + + delta = t2 - t1 + if delta < 0: + t = n = 0 + name = 'seconds' + else: + start = (60 * 60 * 24 * 365) + for name, fac in mapping: + start = start / fac + t = delta / start + t = round(t, 1) + n = int(t) + if n: + break + + name = names[name] + if t > n: + return '%s%s' % (t, name) + else: + return '%s%s' % (n, name) + + +#***************************************************************** +# +#**************************************************************** +if __name__ == '__main__': + import doctest + doctest.testmod() + + + + Added: trunk/fclient/fclient_lib/pyex/numbers.pyc =================================================================== (Binary files differ) Property changes on: trunk/fclient/fclient_lib/pyex/numbers.pyc ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/fclient/fclient_lib/pyex/omapping.py =================================================================== --- trunk/fclient/fclient_lib/pyex/omapping.py (rev 0) +++ trunk/fclient/fclient_lib/pyex/omapping.py 2007-10-27 16:57:28 UTC (rev 13) @@ -0,0 +1,345 @@ +"""Ordered mappings""" + +#****************************************************************************** +# +#****************************************************************************** +class StableMapping(dict): + """Stable mapping wich keeps insertion order of items (neither complete nor whatever) + + >>> StableMapping() + {} + >>> o = StableMapping( ((1, 1), (2, 2)) ) + >>> for i in o: i + (1, 1) + (2, 2) + + >>> enum = o.__iter__() + >>> enum.next() + (1, 1) + + >>> o.popitem(-1) + (2, 2) + + >>> for i in o: i + (1, 1) + + >>> o['a'] = 'aa' + >>> for i in o: i + (1, 1) + ('a', 'aa') + + >>> o['a'] = 'bb' + >>> for i in o: i + (1, 1) + ('a', 'bb') + + >>> o.getitem(-1) + ('a', 'bb') + + >>> del o['a'] + >>> for i in o: i + (1, 1) + + + >>> o.item_order + [1] + + >>> o.remove(1) + >>> for i in o: i + + + """ + + def __init__(self, items=None): + self.item_order = [] + if items is None: + dict.__init__(self) + else: + dict.__init__(self, items) + self.item_order = [i[0] for i in items] + + def __delitem__(self, name): + dict.__delitem__(self, name) + self.item_order.remove(name) + + def __iter__(self): + for i in self.item_order: + yield (i, self[i]) + + def __setitem__(self, name, value): + isnew = name not in self + dict.__setitem__(self, name, value) + if isnew: + self.item_order.append(name) + + def pop(self, name): + item = dict.pop(self, name) + self.item_order.remove(name) + return item + + def popitem(self, i=-1): + item = self.item_order.pop(i) + return (item, dict.pop(self, item)) + + def getitem(self, i): + item = self.item_order[i] + return (item, self[item]) + + def index(self, item): + return self.item_order.index(item) + + def remove(self, item): + del self[item] + +#****************************************************************************** +# +#****************************************************************************** +class MappedPriorityQueue(object): + """Sorted queue supporting priorities and dictionary lookup of items + + >>> PriorityHigh = 2 + >>> PriorityMiddle = 1 + >>> PriorityLow = 0 + + >>> m = MappedPriorityQueue() + >>> for i in range(4): m.push(str(i), 'MyItem-%s' % i, PriorityLow) + >>> len(m) + 4 + >>> [name for (priority, name, item) in m.iter_items()] + ['0', '1', '2', '3'] + + >>> m.set_priority('3', PriorityHigh) + >>> [name for (priority, name, item) in m.iter_items()] + ['3', '0', '1', '2'] + + >>> m.set_priority('2', PriorityMiddle) + >>> [name for (priority, name, item) in m.iter_items()] + ['3', '2', '0', '1'] + + # iterate over all items with PriorityMiddle + >>> for item in m.iter_items(PriorityMiddle, PriorityMiddle): item + (1, '2', 'MyItem-2') + + # pop all items with PriorityMiddle and below + >>> for item in m.pop_items(PriorityMiddle): item + (1, '2', 'MyItem-2') + (0, '0', 'MyItem-0') + (0, '1', 'MyItem-1') + + >>> [name for (priority, name, item) in m.iter_items()] + ['3'] + + + # items can be popped by name aswell + >>> m.push('MyName', 'MyItem', PriorityLow) + >>> [name for (priority, name, item) in m.iter_items()] + ['3', 'MyName'] + + >>> m.pop('MyName') + (0, 'MyName', 'MyItem') + >>> [name for (priority, name, item) in m.iter_items()] + ['3'] + + + # dictionary lookup + >>> m['3'] + 'MyItem-3' + + + # check if queue is empty + >>> bool(m) + True + + >>> m.clear() + >>> bool(m) + False + + """ + + + class QueueItem(object): + + __slots__ = ('item', 'priority', 'name') + + def __init__(self, priority, name, item): + self.item = item + self.priority = priority + self.name = name + + def __cmp__(self,other): + if isinstance(other, self.__class__): + return cmp(self.priority, other.priority) + return cmp(self.priority, other) + + + def __init__(self): + """ + @attr queue: (list) [QueueItem, ...] + @attr mapping: (dict) name --> QueueItem + @note: both mappings should be considered read only + """ + self.queue = [] + self.mapping = {} + + + def __contains__(self, name): + """Returns True if an item 'name' exists in the queue, False otherwise""" + return name in self.mapping + + def __getitem__(self, name): + """Returns an item from the queue given its name""" + return self.mapping[name].item + + def __setitem__(self, name, item): + """Sets the item associated to name""" + self.mapping[name].item = item + + + def __len__(self): + """Retuns the number of items in the queue""" + return len(self.queue) + + def __nonzero__(self): + """Returns False if the queue is empty, True otherwise""" + return bool(self.queue) + + + def clear(self): + """Removes all items from the queue""" + self.queue = [] + self.mapping = {} + + + def get(self, name, default=None): + """Returns an item from the queue given its name or default + @return: tuple(priority, item) + """ + queue_item = self.mapping.get(name, None) + if queue_item is None: + return default + return (queue_item.priority, queue_item.item) + + + def index(self, name): + """Returns the first index of an item in the queue""" + queue_item = self.mapping[name] + return self.queue.index(queue_item) + + + def iter_items(self, priority_start=None, priority_stop=None): + """Iterates over all items in the queue + @param priority_start: minimu priority to start iterating at + or None to start at the first item + @param priority_stop: priority to stop iterating at. If None + all items below priority_start are returned + @return: tuple(priority, name, item) for the next item in turn + """ + for queue_item in self.queue: + if priority_stop is not None: + if queue_item < priority_stop: + raise StopIteration + + if priority_start is not None: + if queue_item > priority_start: + continue + yield (queue_item.priority, queue_item.name, queue_item.item) + + + + def peak(self, index): + """Returns an item from a specified index + @return: tuple(priority, name, item) + """ + queue_item = self.queue[index] + return (queue_item.priority, queue_item.name, queue_item.item) + + + def pop(self, name=None): + """Pops an item from the queue + @param name: name of the item to pop. If None, the item with the highest priority is popped + @return: tuple(priority, name, item) + """ + if name: + queue_item = self.mapping[name] + else: + queue_item = self.queue[0] + self.queue.remove(queue_item) + self.mapping.pop(name) + return (queue_item.priority, queue_item.name, queue_item.item) + + + def pop_items(self, priority_start=None, priority_stop=None): + """Sequentially pops a number of items from the queue + @param priority_start: minimu priority to start popping at + @param priority_stop: priority to stop popping at. If None + all items below priority_start are popped + @return: tuple(name, item) for the next item in turn + """ + i = 0 + while len(self.queue) > i: + queue_item = self.queue[i] + if priority_stop is not None: + if queue_item < priority_stop: + raise StopIteration + + if priority_start is None: + del self.queue[i] + del self.mapping[queue_item.name] + yield (queue_item.name, queue_item.item) + + elif queue_item <= priority_start: + del self.queue[i] + del self.mapping[queue_item.name] + yield (queue_item.priority, queue_item.name, queue_item.item) + + else: + i += 1 + + + def push(self, name, item, priority, sort=True): + """Pushes an item into the queue + @param item: any desired item + @param priority: (int) priority of the item + @param sort: if True the queue is sorted right away, if False, use sort() + to trigger sorting manually + """ + if name in self.mapping: + raise KeyError('Item already exists: %r' % name) + + queue_item = self.QueueItem(priority, name, item) + self.queue.append(queue_item) + self.mapping[name] = queue_item + if sort: + self.sort() + + + def get_priority(self, name): + """Returns the priority of item name""" + return self.mapping[name].priority + + + def set_priority(self, name, priority, sort=True): + """Adjusts the priority of an item + @param name: name of the item to adjust priority for + @param priority: (int) new priority of the item + @param sort: if True the queue is sorted right away, if False, use sort() + to trigger sorting manually + """ + self.mapping[name].priority = priority + if sort: + self.sort() + + + def sort(self): + """Sorts the queue""" + self.queue.sort(reverse=True) + + +#*************************************************************** +# +#*************************************************************** +if __name__ == '__main__': + import doctest + doctest.testmod() + + This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |