source: orange/Orange/utils/__init__.py @ 10998:21975a677988

Revision 10998:21975a677988, 20.4 KB checked in by markotoplak, 18 months ago (diff)

Moved  trac:BuildingFromSource to the repository.

RevLine 
[10580]1"""
2.. index:: utils
3
4Orange.utils contains developer utilities.
5
6------------------
7Reporting progress
8------------------
9
10.. autoclass:: Orange.utils.ConsoleProgressBar
11    :members:
12
13-----------------------------
14Deprecation utility functions
15-----------------------------
16
17.. autofunction:: Orange.utils.deprecation_warning
18
19.. autofunction:: Orange.utils.deprecated_members
20
21.. autofunction:: Orange.utils.deprecated_keywords
22
23.. autofunction:: Orange.utils.deprecated_attribute
24
25.. autofunction:: Orange.utils.deprecated_function_name
26
27----------------
[10654]28Submodules
[10580]29----------------
30
[10581]31.. automodule:: Orange.utils.environ
32
33.. automodule:: Orange.utils.counters
34  :members:
35
36.. automodule:: Orange.utils.render
37  :members:
38
39.. automodule:: Orange.utils.addons
[10580]40
[10654]41.. automodule:: Orange.utils.selection
42
[10580]43"""
44
45__all__ = ["deprecated_members", "deprecated_keywords",
46           "deprecated_attribute", "deprecation_warning",
[10581]47           "deprecated_function_name",
48           "counters", "render", "serverfiles"]
[10580]49
50import environ
51
52def deprecation_warning(old, new, stacklevel=-2):
53    """ Raise a deprecation warning of an obsolete attribute access.
54   
55    :param old: Old attribute name (used in warning message).
56    :param new: New attribute name (used in warning message).
57   
58    """
59    warnings.warn("'%s' is deprecated. Use '%s' instead!" % (old, new), DeprecationWarning, stacklevel=stacklevel)
60   
61# We need to get the instancemethod type
62class _Foo():
63    def bar(self):
64        pass
65instancemethod = type(_Foo.bar)
66del _Foo
67
68function = type(lambda: None)
69
70class universal_set(set):
71    """ A universal set, pretends it contains everything.
72    """
73    def __contains__(self, value):
74        return True
75   
76from functools import wraps
77
78def deprecated_members(name_map, wrap_methods="all", in_place=True):
79    """ Decorate a class with properties for accessing attributes, and methods
80    with deprecated names. In addition methods from the `wrap_methods` list
81    will be wrapped to receive mapped keyword arguments.
82   
83    :param name_map: A dictionary mapping old into new names.
84    :type name_map: dict
85   
86    :param wrap_methods: A list of method names to wrap. Wrapped methods will
87        be called with mapped keyword arguments (by default all methods will
88        be wrapped).
89    :type wrap_methods: list
90   
91    :param in_place: If True the class will be modified in place, otherwise
92        it will be subclassed (default True).
93    :type in_place: bool
94   
95    Example ::
96           
97        >>> class A(object):
98        ...     def __init__(self, foo_bar="bar"):
99        ...         self.set_foo_bar(foo_bar)
100        ...     
101        ...     def set_foo_bar(self, foo_bar="bar"):
102        ...         self.foo_bar = foo_bar
103        ...
104        ... A = deprecated_members(
105        ... {"fooBar": "foo_bar",
106        ...  "setFooBar":"set_foo_bar"},
107        ... wrap_methods=["set_foo_bar", "__init__"])(A)
108        ...
109        ...
110        >>> a = A(fooBar="foo")
111        __main__:1: DeprecationWarning: 'fooBar' is deprecated. Use 'foo_bar' instead!
112        >>> print a.fooBar, a.foo_bar
113        foo foo
114        >>> a.setFooBar("FooBar!")
115        __main__:1: DeprecationWarning: 'setFooBar' is deprecated. Use 'set_foo_bar' instead!
116       
117    .. note:: This decorator does nothing if \
118        :obj:`Orange.utils.environ.orange_no_deprecated_members` environment \
119        variable is set to `True`.
120       
121    """
122    if environ.orange_no_deprecated_members:
123        return lambda cls: cls
124   
125    def is_wrapped(method):
126        """ Is member method already wrapped.
127        """
128        if getattr(method, "_deprecate_members_wrapped", False):
129            return True
130        elif hasattr(method, "im_func"):
131            im_func = method.im_func
132            return getattr(im_func, "_deprecate_members_wrapped", False)
133        else:
134            return False
135       
136    if wrap_methods == "all":
137        wrap_methods = universal_set()
138    elif not wrap_methods:
139        wrap_methods = set()
140       
141    def wrapper(cls):
142        cls_names = {}
143        # Create properties for accessing deprecated members
144        for old_name, new_name in name_map.items():
145            cls_names[old_name] = deprecated_attribute(old_name, new_name)
146           
147        # wrap member methods to map keyword arguments
148        for key, value in cls.__dict__.items():
149            if isinstance(value, (instancemethod, function)) \
150                and not is_wrapped(value) and key in wrap_methods:
151               
152                wrapped = deprecated_keywords(name_map)(value)
153                wrapped._deprecate_members_wrapped = True # A flag indicating this function already maps keywords
154                cls_names[key] = wrapped
155        if in_place:
156            for key, val in cls_names.items():
157                setattr(cls, key, val)
158            return cls
159        else:
160            return type(cls.__name__, (cls,), cls_names)
161       
162    return wrapper
163
164def deprecated_keywords(name_map):
165    """ Deprecates the keyword arguments of the function.
166   
167    Example ::
168   
169        >>> @deprecated_keywords({"myArg": "my_arg"})
170        ... def my_func(my_arg=None):
171        ...     print my_arg
172        ...
173        ...
174        >>> my_func(myArg="Arg")
175        __main__:1: DeprecationWarning: 'myArg' is deprecated. Use 'my_arg' instead!
176        Arg
177       
178    .. note:: This decorator does nothing if \
179        :obj:`Orange.utils.environ.orange_no_deprecated_members` environment \
180        variable is set to `True`.
181       
182    """
183    if environ.orange_no_deprecated_members:
184        return lambda func: func
185    for name in name_map.values():
186        if name in name_map:
187            raise ValueError("Deprecation keys and values overlap; this could"
188                             " cause trouble!")
189   
190    def decorator(func):
191        @wraps(func)
192        def wrap_call(*args, **kwargs):
193            kwargs = dict(kwargs)
194            for name in name_map:
195                if name in kwargs:
196                    deprecation_warning(name, name_map[name], stacklevel=3)
197                    kwargs[name_map[name]] = kwargs[name]
198                    del kwargs[name]
199            return func(*args, **kwargs)
200        return wrap_call
201    return decorator
202
203def deprecated_attribute(old_name, new_name):
204    """ Return a property object that accesses an attribute named `new_name`
205    and raises a deprecation warning when doing so.
206
207    ..
208
209        >>> sys.stderr = sys.stdout
210   
211    Example ::
212   
213        >>> class A(object):
214        ...     def __init__(self):
215        ...         self.my_attr = "123"
216        ...     myAttr = deprecated_attribute("myAttr", "my_attr")
217        ...
218        ...
219        >>> a = A()
220        >>> print a.myAttr
221        ...:1: DeprecationWarning: 'myAttr' is deprecated. Use 'my_attr' instead!
222        123
223       
224    .. note:: This decorator does nothing and returns None if \
225        :obj:`Orange.utils.environ.orange_no_deprecated_members` environment \
226        variable is set to `True`.
227       
228    """
229    if environ.orange_no_deprecated_members:
230        return None
231   
232    def fget(self):
233        deprecation_warning(old_name, new_name, stacklevel=3)
234        return getattr(self, new_name)
235   
236    def fset(self, value):
237        deprecation_warning(old_name, new_name, stacklevel=3)
238        setattr(self, new_name, value)
239   
240    def fdel(self):
241        deprecation_warning(old_name, new_name, stacklevel=3)
242        delattr(self, new_name)
243   
244    prop = property(fget, fset, fdel,
245                    doc="A deprecated member '%s'. Use '%s' instead." % (old_name, new_name))
246    return prop
247
248class class_property(object):
249    def __init__(self, fget=None, fset=None, fdel=None, doc="class property"):
250        self.fget = fget
251        self.fset = fset
252        self.fdel = fdel
253        self.__doc__ = doc
254       
255    def __get__(self, instance, owner):
256        if instance is None:
257            return self.fget(owner)
258        else:
259            return self.fget(instance)               
260           
261def deprecated_class_attribute(old_name, new_name):
262    """ Return a property object that accesses an class attribute
263    named `new_name` and raises a deprecation warning when doing so.
264   
265    """
266    if environ.orange_no_deprecated_members:
267        return None
268   
269    def fget(self):
270        deprecation_warning(old_name, new_name, stacklevel=3)
271        return getattr(self, new_name)
272       
273    prop = class_property(fget,
274                    doc="A deprecated class member '%s'. Use '%s' instead." % (old_name, new_name))
275    return prop
276
277def deprecated_function_name(func):
278    """ Return a wrapped function that raises an deprecation warning when
279    called. This should be used for deprecation of module level function names.
280   
281    Example ::
282   
283        >>> def func_a(arg):
284        ...    print "This is func_a  (used to be named funcA) called with", arg
285        ...
286        ...
287        >>> funcA = deprecated_function_name(func_a)
288        >>> funcA(None)
289         
290   
291    .. note:: This decorator does nothing and if \
292        :obj:`Orange.utils.environ.orange_no_deprecated_members` environment \
293        variable is set to `True`.
294       
295    """
296    if environ.orange_no_deprecated_members:
297        return func
298   
299    @wraps(func)
300    def wrapped(*args, **kwargs):
301        warnings.warn("Deprecated function name. Use %r instead!" % func.__name__,
302                      DeprecationWarning, stacklevel=2)
303        return func(*args, **kwargs)
304    return wrapped
305   
306class ConsoleProgressBar(object):
307    """ A class to for printing progress bar reports in the console.
308   
309    Example ::
310   
311        >>> import sys, time
312        >>> progress = ConsoleProgressBar("Example", output=sys.stdout)
313        >>> for i in range(100):
314        ...    progress.advance()
315        ...    # Or progress.set_state(i)
316        ...    time.sleep(0.01)
317        ...
318        ...
319        Example ===================================>100%
320       
321    """
322    def __init__(self, title="", charwidth=40, step=1, output=None):
323        """ Initialize the progress bar.
324       
325        :param title: The title for the progress bar.
326        :type title: str
327        :param charwidth: The maximum progress bar width in characters.
328       
329            .. todo:: Get the console width from the ``output`` if the
330                information can be retrieved.
331               
332        :type charwidth: int
333        :param step: A default step used if ``advance`` is called without
334            any  arguments
335       
336        :type step: int
337        :param output: The output file. If None (default) then ``sys.stderr``
338            is used.
339           
340        :type output: An file like object to print the progress report to.
341         
342        """
343        self.title = title + " "
344        self.charwidth = charwidth
345        self.step = step
346        self.currstring = ""
347        self.state = 0
348        if output is None:
349            output = sys.stderr
350        self.output = output
351
352    def clear(self, i=-1):
353        """ Clear the current progress line indicator string.
354        """
355        try:
356            if hasattr(self.output, "isatty") and self.output.isatty():
357                self.output.write("\b" * (i if i != -1 else len(self.currstring)))
358            else:
359                self.output.seek(-i if i != -1 else -len(self.currstring), 2)
360        except Exception: ## If for some reason we failed
361            self.output.write("\n")
362
363    def getstring(self):
364        """ Return the progress indicator string.
365        """
366        progchar = int(round(float(self.state) * (self.charwidth - 5) / 100.0))
367        return self.title + "=" * (progchar) + ">" + " " * (self.charwidth\
368            - 5 - progchar) + "%3i" % int(round(self.state)) + "%"
369
370    def printline(self, string):
371        """ Print the ``string`` to the output file.
372        """
373        try:
374            self.clear()
375            self.output.write(string)
376            self.output.flush()
377        except Exception:
378            pass
379        self.currstring = string
380
381    def __call__(self, newstate=None):
382        """ Set the ``newstate`` as the current state of the progress bar.
383        ``newstate`` must be in the interval [0, 100].
384       
385        .. note:: ``set_state`` is the prefered way to set a new steate.
386       
387        :param newstate: The new state of the progress bar.
388        :type newstate: float
389         
390        """
391        if newstate is None:
392            self.advance()
393        else:
394            self.set_state(newstate)
395           
396    def set_state(self, newstate):
397        """ Set the ``newstate`` as the current state of the progress bar.
398        ``newstate`` must be in the interval [0, 100].
399       
400        :param newstate: The new state of the progress bar.
401        :type newstate: float
402       
403        """
404        if int(newstate) != int(self.state):
405            self.state = newstate
406            self.printline(self.getstring())
407        else:
408            self.state = newstate
409           
410    def advance(self, step=None):
411        """ Advance the current state by ``step``. If ``step`` is None use
412        the default step as set at class initialization.
413         
414        """
415        if step is None:
416            step = self.step
417           
418        newstate = self.state + step
419        self.set_state(newstate)
420
421    def finish(self):
422        """ Finish the progress bar (i.e. set the state to 100 and
423        print the final newline to the ``output`` file).
424        """
425        self.__call__(100)
426        self.output.write("\n")
427
428def progress_bar_milestones(count, iterations=100):
429    return set([int(i*count/float(iterations)) for i in range(iterations)])
430
431progressBarMilestones = deprecated_function_name(progress_bar_milestones)
[10581]432
433import random, types, sys
434import time
435
436def getobjectname(x, default=""):
437    if type(x)==types.StringType:
438        return x
439     
440    for i in ["name", "shortDescription", "description", "func_doc", "func_name"]:
441        if getattr(x, i, ""):
442            return getattr(x, i)
443
444    if hasattr(x, "__class__"):
445        r = repr(x.__class__)
446        if r[1:5]=="type":
447            return str(x.__class__)[7:-2]
448        elif r[1:6]=="class":
449            return str(x.__class__)[8:-2]
450    return default
451
452
453def demangle_examples(x):
454    if type(x)==types.TupleType:
455        return x
456    else:
457        return x, 0
458
459def frange(*argw):
460    """ Like builtin `range` but works with floats
461    """
462    start, stop, step = 0.0, 1.0, 0.1
463    if len(argw)==1:
464        start=step=argw[0]
465    elif len(argw)==2:
466        stop, step = argw
467    elif len(argw)==3:
468        start, stop, step = argw
469    elif len(argw)>3:
470        raise AttributeError, "1-3 arguments expected"
471
472    stop+=1e-10
473    i=0
474    res=[]
475    while 1:
476        f=start+i*step
477        if f>stop:
478            break
479        res.append(f)
480        i+=1
481    return res
482
483verbose = 0
484
485def print_verbose(text, *verb):
486    if len(verb) and verb[0] or verbose:
487        print text
488
489def lru_cache(maxsize=100):
490    """ A least recently used cache function decorator.
491    (Similar to the functools.lru_cache in python 3.2)
492    """
493   
494    def decorating_function(func):
495        import functools
496        cache = {}
497       
498        @functools.wraps(func)
499        def wrapped(*args, **kwargs):
500            key = args + tuple(sorted(kwargs.items()))
501            if key not in cache:
502                res = func(*args, **kwargs)
503                cache[key] = (time.time(), res)
504                if len(cache) > maxsize:
505                    key, (_, _) = min(cache.iteritems(), key=lambda item: item[1][0])
506                    del cache[key]
507            else:
508                _, res = cache[key]
509                cache[key] = (time.time(), res) # update the time
510               
511            return res
512       
513        def clear():
514            cache.clear()
515       
516        wrapped.clear = clear
517        wrapped._cache = cache
518       
519        return wrapped
520    return decorating_function
521
522#from Orange.misc.render import contextmanager
523from contextlib import contextmanager
524
525@contextmanager
526def member_set(obj, name, val):
527    """ A context manager that sets member ``name`` on ``obj`` to ``val``
528    and restores the previous value on exit.
529    """
530    old_val = getattr(obj, name, val)
531    setattr(obj, name, val)
532    yield
533    setattr(obj, name, old_val)
534   
535   
536class recursion_limit(object):
537    """ A context manager that sets a new recursion limit.
538   
539    """
540    def __init__(self, limit=1000):
541        self.limit = limit
542       
543    def __enter__(self):
544        self.old_limit = sys.getrecursionlimit()
545        sys.setrecursionlimit(self.limit)
546   
547    def __exit__(self, exc_type, exc_val, exc_tb):
548        sys.setrecursionlimit(self.old_limit)
549       
550"""
551Some utility functions common to Orange classes.
[10937]552
[10581]553"""
554
[10937]555
556def _orange_learner__new__(base):
557    """Return an 'schizophrenic' __new__ class method following
558    `Orange.core.Lerner.__new__` calling convention.
559
560    :param base: base class.
[10581]561    :type base: type
[10937]562
[10581]563    """
[10937]564    import Orange
565
[10581]566    @wraps(base.__new__)
[10937]567    def _orange_learner__new__wrapped(cls, data=None, weight=0, **kwargs):
568        self = base.__new__(cls, **kwargs)
569        if data is not None:
[10581]570            self.__init__(**kwargs)
[10937]571            return self.__call__(data, weight)
[10581]572        else:
573            return self
[10937]574
575    return _orange_learner__new__wrapped
576
577
578def _orange__new__(base=None):
579    """Return an orange 'schizophrenic' __new__ class method.
580
581    :param base: base orange class (default `Orange.core.Learner`)
582    :type base: type
583
584    Example::
585        class NewOrangeLearner(Orange.core.Learner):
586            __new__ = _orange__new(Orange.core.Learner)
587
588    """
589    import Orange
590    if base is None:
591        base = Orange.core.Learner
592
593    if issubclass(base, Orange.core.Learner):
594        return _orange_learner__new__(base)
595    else:
596        @wraps(base.__new__)
597        def _orange__new_wrapped(cls, data=None, **kwargs):
598            if base == object:
599                self = base.__new__(cls)
600            else:
601                self = base.__new__(cls, **kwargs)
602
603            if data:
604                self.__init__(**kwargs)
605                return self.__call__(data)
606            else:
607                return self
608
[10581]609    return _orange__new_wrapped
610
611
612def _orange__reduce__(self):
613    """ A default __reduce__ method for orange types. Assumes the object
614    can be reconstructed with the call `constructor(__dict__)` where __dict__
615    if the stored (pickled) __dict__ attribute.
[10937]616
[10581]617    Example::
[10937]618        class NewOrangeType(Orange.core.Learner):
[10581]619            __reduce__ = _orange__reduce()
[10937]620    """
[10581]621    return type(self), (), dict(self.__dict__)
622
623demangleExamples = deprecated_function_name(demangle_examples)
624printVerbose = deprecated_function_name(print_verbose)
625
[10981]626import urllib2
[10998]627import posixpath
628import os
629
630from contextlib import contextmanager
631import StringIO
632
633@contextmanager
634def finishing(obj):
635    """ Calls obj.finish() on context exit.
636    """
637    yield obj
638    obj.finish()
639
640def guess_size(fileobj):
641    try:
642        if isinstance(fileobj, file):
643            return os.fstat(fileobj.fileno()).st_size
644        elif isinstance(fileobj, StringIO.StringIO):
645            pos = fileobj.tell()
646            fileobj.seek(0, 2)
647            length = fileobj.tell() - pos
648            fileobj.seek(pos, 0)
649            return length
650        elif isinstance(fileobj, urllib.addinfourl):
651            length = fileobj.headers.get("content-length", None)
652            return length
653    except Exception, ex:
654        pass
[10981]655
656def copyfileobj(src, dst, buffer=2**10, content_len=None, progress=None):
657    """
658    shutil.copyfileobj with progress reporting.
659    """
660    count = 0
661    if content_len is None:
662        content_len = guess_size(src) or sys.maxint
663    while True:
664        data = src.read(buffer)
665        dst.write(data)
666        count += len(data)
667        if progress:
668            progress(100.0 * count / content_len)
669        if not data:
670            break
671           
672def wget(url, directory=".", dst_obj=None, progress=None):
673    stream = urllib2.urlopen(url)
674    length = stream.headers.get("content-length", None)
675    if length is None:
676        length = sys.maxint
677    else:
678        length = int(length)
679   
680    basename = posixpath.basename(url)
681       
682    if dst_obj is None:
683        dst_obj = open(os.path.join(directory, basename), "wb")
684   
685    if progress == True:
686        from Orange.utils import ConsoleProgressBar
687        progress = ConsoleProgressBar("Downloading %r." % basename)
688        with finishing(progress):
689            copyfileobj(stream, dst_obj, buffer=2**10, content_len=length,
690                        progress=progress)
691    else:
692        copyfileobj(stream, dst_obj, buffer=2**10, content_len=length,
693                    progress=progress)
694   
695
[10654]696import warnings
697
698import selection
Note: See TracBrowser for help on using the repository browser.