source: orange/Orange/utils/__init__.py @ 10580:c4cbae8dcf8b

Revision 10580:c4cbae8dcf8b, 13.3 KB checked in by markotoplak, 2 years ago (diff)

Moved deprecation functions, progress bar support and environ into Orange.utils. Orange imports cleanly, although it is not tested yet.

Line 
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----------------
28Other submodules
29----------------
30
31..automodule:: Orange.utils.environ
32
33"""
34
35"""
36__all__ = ["deprecated_members", "deprecated_keywords",
37           "deprecated_attribute", "deprecation_warning",
38           "deprecated_function_name"]
39"""
40
41import environ
42
43import warnings
44def deprecation_warning(old, new, stacklevel=-2):
45    """ Raise a deprecation warning of an obsolete attribute access.
46   
47    :param old: Old attribute name (used in warning message).
48    :param new: New attribute name (used in warning message).
49   
50    """
51    warnings.warn("'%s' is deprecated. Use '%s' instead!" % (old, new), DeprecationWarning, stacklevel=stacklevel)
52   
53# We need to get the instancemethod type
54class _Foo():
55    def bar(self):
56        pass
57instancemethod = type(_Foo.bar)
58del _Foo
59
60function = type(lambda: None)
61
62class universal_set(set):
63    """ A universal set, pretends it contains everything.
64    """
65    def __contains__(self, value):
66        return True
67   
68from functools import wraps
69
70def deprecated_members(name_map, wrap_methods="all", in_place=True):
71    """ Decorate a class with properties for accessing attributes, and methods
72    with deprecated names. In addition methods from the `wrap_methods` list
73    will be wrapped to receive mapped keyword arguments.
74   
75    :param name_map: A dictionary mapping old into new names.
76    :type name_map: dict
77   
78    :param wrap_methods: A list of method names to wrap. Wrapped methods will
79        be called with mapped keyword arguments (by default all methods will
80        be wrapped).
81    :type wrap_methods: list
82   
83    :param in_place: If True the class will be modified in place, otherwise
84        it will be subclassed (default True).
85    :type in_place: bool
86   
87    Example ::
88           
89        >>> class A(object):
90        ...     def __init__(self, foo_bar="bar"):
91        ...         self.set_foo_bar(foo_bar)
92        ...     
93        ...     def set_foo_bar(self, foo_bar="bar"):
94        ...         self.foo_bar = foo_bar
95        ...
96        ... A = deprecated_members(
97        ... {"fooBar": "foo_bar",
98        ...  "setFooBar":"set_foo_bar"},
99        ... wrap_methods=["set_foo_bar", "__init__"])(A)
100        ...
101        ...
102        >>> a = A(fooBar="foo")
103        __main__:1: DeprecationWarning: 'fooBar' is deprecated. Use 'foo_bar' instead!
104        >>> print a.fooBar, a.foo_bar
105        foo foo
106        >>> a.setFooBar("FooBar!")
107        __main__:1: DeprecationWarning: 'setFooBar' is deprecated. Use 'set_foo_bar' instead!
108       
109    .. note:: This decorator does nothing if \
110        :obj:`Orange.utils.environ.orange_no_deprecated_members` environment \
111        variable is set to `True`.
112       
113    """
114    if environ.orange_no_deprecated_members:
115        return lambda cls: cls
116   
117    def is_wrapped(method):
118        """ Is member method already wrapped.
119        """
120        if getattr(method, "_deprecate_members_wrapped", False):
121            return True
122        elif hasattr(method, "im_func"):
123            im_func = method.im_func
124            return getattr(im_func, "_deprecate_members_wrapped", False)
125        else:
126            return False
127       
128    if wrap_methods == "all":
129        wrap_methods = universal_set()
130    elif not wrap_methods:
131        wrap_methods = set()
132       
133    def wrapper(cls):
134        cls_names = {}
135        # Create properties for accessing deprecated members
136        for old_name, new_name in name_map.items():
137            cls_names[old_name] = deprecated_attribute(old_name, new_name)
138           
139        # wrap member methods to map keyword arguments
140        for key, value in cls.__dict__.items():
141            if isinstance(value, (instancemethod, function)) \
142                and not is_wrapped(value) and key in wrap_methods:
143               
144                wrapped = deprecated_keywords(name_map)(value)
145                wrapped._deprecate_members_wrapped = True # A flag indicating this function already maps keywords
146                cls_names[key] = wrapped
147        if in_place:
148            for key, val in cls_names.items():
149                setattr(cls, key, val)
150            return cls
151        else:
152            return type(cls.__name__, (cls,), cls_names)
153       
154    return wrapper
155
156def deprecated_keywords(name_map):
157    """ Deprecates the keyword arguments of the function.
158   
159    Example ::
160   
161        >>> @deprecated_keywords({"myArg": "my_arg"})
162        ... def my_func(my_arg=None):
163        ...     print my_arg
164        ...
165        ...
166        >>> my_func(myArg="Arg")
167        __main__:1: DeprecationWarning: 'myArg' is deprecated. Use 'my_arg' instead!
168        Arg
169       
170    .. note:: This decorator does nothing if \
171        :obj:`Orange.utils.environ.orange_no_deprecated_members` environment \
172        variable is set to `True`.
173       
174    """
175    if environ.orange_no_deprecated_members:
176        return lambda func: func
177    for name in name_map.values():
178        if name in name_map:
179            raise ValueError("Deprecation keys and values overlap; this could"
180                             " cause trouble!")
181   
182    def decorator(func):
183        @wraps(func)
184        def wrap_call(*args, **kwargs):
185            kwargs = dict(kwargs)
186            for name in name_map:
187                if name in kwargs:
188                    deprecation_warning(name, name_map[name], stacklevel=3)
189                    kwargs[name_map[name]] = kwargs[name]
190                    del kwargs[name]
191            return func(*args, **kwargs)
192        return wrap_call
193    return decorator
194
195def deprecated_attribute(old_name, new_name):
196    """ Return a property object that accesses an attribute named `new_name`
197    and raises a deprecation warning when doing so.
198
199    ..
200
201        >>> sys.stderr = sys.stdout
202   
203    Example ::
204   
205        >>> class A(object):
206        ...     def __init__(self):
207        ...         self.my_attr = "123"
208        ...     myAttr = deprecated_attribute("myAttr", "my_attr")
209        ...
210        ...
211        >>> a = A()
212        >>> print a.myAttr
213        ...:1: DeprecationWarning: 'myAttr' is deprecated. Use 'my_attr' instead!
214        123
215       
216    .. note:: This decorator does nothing and returns None if \
217        :obj:`Orange.utils.environ.orange_no_deprecated_members` environment \
218        variable is set to `True`.
219       
220    """
221    if environ.orange_no_deprecated_members:
222        return None
223   
224    def fget(self):
225        deprecation_warning(old_name, new_name, stacklevel=3)
226        return getattr(self, new_name)
227   
228    def fset(self, value):
229        deprecation_warning(old_name, new_name, stacklevel=3)
230        setattr(self, new_name, value)
231   
232    def fdel(self):
233        deprecation_warning(old_name, new_name, stacklevel=3)
234        delattr(self, new_name)
235   
236    prop = property(fget, fset, fdel,
237                    doc="A deprecated member '%s'. Use '%s' instead." % (old_name, new_name))
238    return prop
239
240class class_property(object):
241    def __init__(self, fget=None, fset=None, fdel=None, doc="class property"):
242        self.fget = fget
243        self.fset = fset
244        self.fdel = fdel
245        self.__doc__ = doc
246       
247    def __get__(self, instance, owner):
248        if instance is None:
249            return self.fget(owner)
250        else:
251            return self.fget(instance)               
252           
253def deprecated_class_attribute(old_name, new_name):
254    """ Return a property object that accesses an class attribute
255    named `new_name` and raises a deprecation warning when doing so.
256   
257    """
258    if environ.orange_no_deprecated_members:
259        return None
260   
261    def fget(self):
262        deprecation_warning(old_name, new_name, stacklevel=3)
263        return getattr(self, new_name)
264       
265    prop = class_property(fget,
266                    doc="A deprecated class member '%s'. Use '%s' instead." % (old_name, new_name))
267    return prop
268
269def deprecated_function_name(func):
270    """ Return a wrapped function that raises an deprecation warning when
271    called. This should be used for deprecation of module level function names.
272   
273    Example ::
274   
275        >>> def func_a(arg):
276        ...    print "This is func_a  (used to be named funcA) called with", arg
277        ...
278        ...
279        >>> funcA = deprecated_function_name(func_a)
280        >>> funcA(None)
281         
282   
283    .. note:: This decorator does nothing and if \
284        :obj:`Orange.utils.environ.orange_no_deprecated_members` environment \
285        variable is set to `True`.
286       
287    """
288    if environ.orange_no_deprecated_members:
289        return func
290   
291    @wraps(func)
292    def wrapped(*args, **kwargs):
293        warnings.warn("Deprecated function name. Use %r instead!" % func.__name__,
294                      DeprecationWarning, stacklevel=2)
295        return func(*args, **kwargs)
296    return wrapped
297   
298class ConsoleProgressBar(object):
299    """ A class to for printing progress bar reports in the console.
300   
301    Example ::
302   
303        >>> import sys, time
304        >>> progress = ConsoleProgressBar("Example", output=sys.stdout)
305        >>> for i in range(100):
306        ...    progress.advance()
307        ...    # Or progress.set_state(i)
308        ...    time.sleep(0.01)
309        ...
310        ...
311        Example ===================================>100%
312       
313    """
314    def __init__(self, title="", charwidth=40, step=1, output=None):
315        """ Initialize the progress bar.
316       
317        :param title: The title for the progress bar.
318        :type title: str
319        :param charwidth: The maximum progress bar width in characters.
320       
321            .. todo:: Get the console width from the ``output`` if the
322                information can be retrieved.
323               
324        :type charwidth: int
325        :param step: A default step used if ``advance`` is called without
326            any  arguments
327       
328        :type step: int
329        :param output: The output file. If None (default) then ``sys.stderr``
330            is used.
331           
332        :type output: An file like object to print the progress report to.
333         
334        """
335        self.title = title + " "
336        self.charwidth = charwidth
337        self.step = step
338        self.currstring = ""
339        self.state = 0
340        if output is None:
341            output = sys.stderr
342        self.output = output
343
344    def clear(self, i=-1):
345        """ Clear the current progress line indicator string.
346        """
347        try:
348            if hasattr(self.output, "isatty") and self.output.isatty():
349                self.output.write("\b" * (i if i != -1 else len(self.currstring)))
350            else:
351                self.output.seek(-i if i != -1 else -len(self.currstring), 2)
352        except Exception: ## If for some reason we failed
353            self.output.write("\n")
354
355    def getstring(self):
356        """ Return the progress indicator string.
357        """
358        progchar = int(round(float(self.state) * (self.charwidth - 5) / 100.0))
359        return self.title + "=" * (progchar) + ">" + " " * (self.charwidth\
360            - 5 - progchar) + "%3i" % int(round(self.state)) + "%"
361
362    def printline(self, string):
363        """ Print the ``string`` to the output file.
364        """
365        try:
366            self.clear()
367            self.output.write(string)
368            self.output.flush()
369        except Exception:
370            pass
371        self.currstring = string
372
373    def __call__(self, newstate=None):
374        """ Set the ``newstate`` as the current state of the progress bar.
375        ``newstate`` must be in the interval [0, 100].
376       
377        .. note:: ``set_state`` is the prefered way to set a new steate.
378       
379        :param newstate: The new state of the progress bar.
380        :type newstate: float
381         
382        """
383        if newstate is None:
384            self.advance()
385        else:
386            self.set_state(newstate)
387           
388    def set_state(self, newstate):
389        """ Set the ``newstate`` as the current state of the progress bar.
390        ``newstate`` must be in the interval [0, 100].
391       
392        :param newstate: The new state of the progress bar.
393        :type newstate: float
394       
395        """
396        if int(newstate) != int(self.state):
397            self.state = newstate
398            self.printline(self.getstring())
399        else:
400            self.state = newstate
401           
402    def advance(self, step=None):
403        """ Advance the current state by ``step``. If ``step`` is None use
404        the default step as set at class initialization.
405         
406        """
407        if step is None:
408            step = self.step
409           
410        newstate = self.state + step
411        self.set_state(newstate)
412
413    def finish(self):
414        """ Finish the progress bar (i.e. set the state to 100 and
415        print the final newline to the ``output`` file).
416        """
417        self.__call__(100)
418        self.output.write("\n")
419
420def progress_bar_milestones(count, iterations=100):
421    return set([int(i*count/float(iterations)) for i in range(iterations)])
422
423progressBarMilestones = deprecated_function_name(progress_bar_milestones)
Note: See TracBrowser for help on using the repository browser.