[pygobject] Add deprecation warnings and cleanup class initializer overrides



commit 86a37d67455dc5d435ade35f17b27c5de2b288f5
Author: Simon Feltman <sfeltman src gnome org>
Date:   Tue Aug 13 18:02:54 2013 -0700

    Add deprecation warnings and cleanup class initializer overrides
    
    Print deprecation warnings for calls to class initializers which
    don't explicitly specify keywords. Print deprecation warning
    for overrides that have renamed keywords (Gtk.Table.rows should
    be n_rows). Additionally deprecate non-standard defaults with
    initializers (Gtk.SizeGroup.mode defaults to HORIZONTAL in GTK+
    and VERTICAL in PyGI).
    Remove AboutDialog override because it doesn't do anything.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=705810

 gi/overrides/Gio.py         |    6 +-
 gi/overrides/Gtk.py         |  367 +++++++++++++++++++++----------------------
 gi/overrides/__init__.py    |   88 ++++++++++
 tests/test_gi.py            |   75 +++++++++
 tests/test_overrides_gtk.py |   95 ++++++++----
 5 files changed, 411 insertions(+), 220 deletions(-)
---
diff --git a/gi/overrides/Gio.py b/gi/overrides/Gio.py
index 6ecd1c4..5a5dd2f 100644
--- a/gi/overrides/Gio.py
+++ b/gi/overrides/Gio.py
@@ -18,7 +18,7 @@
 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
 # USA
 
-from ..overrides import override
+from ..overrides import override, deprecated_init
 from ..module import get_introspection_module
 
 from gi.repository import GLib
@@ -63,8 +63,8 @@ __all__.append('MenuItem')
 class Settings(Gio.Settings):
     '''Provide dictionary-like access to GLib.Settings.'''
 
-    def __init__(self, schema, path=None, backend=None, **kwargs):
-        Gio.Settings.__init__(self, schema=schema, backend=backend, path=path, **kwargs)
+    __init__ = deprecated_init(Gio.Settings.__init__,
+                               arg_names=('schema', 'path', 'backend'))
 
     def __contains__(self, key):
         return key in self.list_keys()
diff --git a/gi/overrides/Gtk.py b/gi/overrides/Gtk.py
index e774c0c..58251de 100644
--- a/gi/overrides/Gtk.py
+++ b/gi/overrides/Gtk.py
@@ -21,8 +21,10 @@
 
 import collections
 import sys
+import warnings
+
 from gi.repository import GObject
-from ..overrides import override, strip_boolean_result
+from ..overrides import override, strip_boolean_result, deprecated_init
 from ..module import get_introspection_module
 from gi import PyGIDeprecationWarning
 
@@ -38,7 +40,6 @@ Gtk = get_introspection_module('Gtk')
 __all__ = []
 
 if Gtk._version == '2.0':
-    import warnings
     warn_msg = "You have imported the Gtk 2.0 module.  Because Gtk 2.0 \
 was not designed for use with introspection some of the \
 interfaces and API will fail.  As such this is not supported \
@@ -49,6 +50,12 @@ python module to use with Gtk 2.0"
     warnings.warn(warn_msg, RuntimeWarning)
 
 
+class PyGTKDeprecationWarning(PyGIDeprecationWarning):
+    pass
+
+__all__.append('PyGTKDeprecationWarning')
+
+
 def _construct_target_list(targets):
     """Create a list of TargetEntry items from a list of tuples in the form (target, flags, info)
 
@@ -124,24 +131,27 @@ __all__.append("Editable")
 
 
 class Action(Gtk.Action):
-    def __init__(self, name, label, tooltip, stock_id, **kwds):
-        Gtk.Action.__init__(self, name=name, label=label, tooltip=tooltip, stock_id=stock_id, **kwds)
+    __init__ = deprecated_init(Gtk.Action.__init__,
+                               arg_names=('name', 'label', 'tooltip', 'stock_id'),
+                               category=PyGTKDeprecationWarning)
 
 Action = override(Action)
 __all__.append("Action")
 
 
 class RadioAction(Gtk.RadioAction):
-    def __init__(self, name, label, tooltip, stock_id, value, **kwds):
-        Gtk.RadioAction.__init__(self, name=name, label=label, tooltip=tooltip, stock_id=stock_id, 
value=value, **kwds)
+    __init__ = deprecated_init(Gtk.RadioAction.__init__,
+                               arg_names=('name', 'label', 'tooltip', 'stock_id', 'value'),
+                               category=PyGTKDeprecationWarning)
 
 RadioAction = override(RadioAction)
 __all__.append("RadioAction")
 
 
 class ActionGroup(Gtk.ActionGroup):
-    def __init__(self, name, **kwds):
-        super(ActionGroup, self).__init__(name=name, **kwds)
+    __init__ = deprecated_init(Gtk.ActionGroup.__init__,
+                               arg_names=('name',),
+                               category=PyGTKDeprecationWarning)
 
     def add_actions(self, entries, user_data=None):
         """
@@ -175,7 +185,7 @@ class ActionGroup(Gtk.ActionGroup):
             raise TypeError('entries must be iterable')
 
         def _process_action(name, stock_id=None, label=None, accelerator=None, tooltip=None, callback=None):
-            action = Action(name, label, tooltip, stock_id)
+            action = Action(name=name, label=label, tooltip=tooltip, stock_id=stock_id)
             if callback is not None:
                 if user_data is None:
                     action.connect('activate', callback)
@@ -223,7 +233,7 @@ class ActionGroup(Gtk.ActionGroup):
             raise TypeError('entries must be iterable')
 
         def _process_action(name, stock_id=None, label=None, accelerator=None, tooltip=None, callback=None, 
is_active=False):
-            action = Gtk.ToggleAction(name, label, tooltip, stock_id)
+            action = Gtk.ToggleAction(name=name, label=label, tooltip=tooltip, stock_id=stock_id)
             action.set_active(is_active)
             if callback is not None:
                 if user_data is None:
@@ -273,7 +283,7 @@ class ActionGroup(Gtk.ActionGroup):
         first_action = None
 
         def _process_action(group_source, name, stock_id=None, label=None, accelerator=None, tooltip=None, 
entry_value=0):
-            action = RadioAction(name, label, tooltip, stock_id, entry_value)
+            action = RadioAction(name=name, label=label, tooltip=tooltip, stock_id=stock_id, 
value=entry_value)
 
             # FIXME: join_group is a patch to Gtk+ 3.0
             #        otherwise we can't effectively add radio actions to a
@@ -329,29 +339,28 @@ __all__.append('ComboBox')
 
 
 class Box(Gtk.Box):
-    def __init__(self, homogeneous=False, spacing=0, **kwds):
-        super(Box, self).__init__(**kwds)
-        self.set_homogeneous(homogeneous)
-        self.set_spacing(spacing)
+    __init__ = deprecated_init(Gtk.Box.__init__,
+                               arg_names=('homogeneous', 'spacing'),
+                               category=PyGTKDeprecationWarning)
 
 Box = override(Box)
 __all__.append('Box')
 
 
 class SizeGroup(Gtk.SizeGroup):
-    def __init__(self, mode=Gtk.SizeGroupMode.VERTICAL):
-        super(SizeGroup, self).__init__(mode=mode)
+    __init__ = deprecated_init(Gtk.SizeGroup.__init__,
+                               arg_names=('mode',),
+                               deprecated_defaults={'mode': Gtk.SizeGroupMode.VERTICAL},
+                               category=PyGTKDeprecationWarning)
 
 SizeGroup = override(SizeGroup)
 __all__.append('SizeGroup')
 
 
 class MenuItem(Gtk.MenuItem):
-    def __init__(self, label=None, **kwds):
-        if label:
-            super(MenuItem, self).__init__(label=label, **kwds)
-        else:
-            super(MenuItem, self).__init__(**kwds)
+    __init__ = deprecated_init(Gtk.MenuItem.__init__,
+                               arg_names=('label',),
+                               category=PyGTKDeprecationWarning)
 
 MenuItem = override(MenuItem)
 __all__.append('MenuItem')
@@ -432,56 +441,66 @@ __all__.append('Builder')
 
 
 class Window(Gtk.Window):
-    def __init__(self, type=Gtk.WindowType.TOPLEVEL, **kwds):
-        if not initialized:
-            raise RuntimeError("Gtk couldn't be initialized")
-
-        # type is a construct-only property; if it is already set (e. g. by
-        # GtkBuilder), do not try to set it again and just ignore it
-        try:
-            self.get_property('type')
-            Gtk.Window.__init__(self, **kwds)
-        except TypeError:
-            Gtk.Window.__init__(self, type=type, **kwds)
+    __init__ = deprecated_init(Gtk.Window.__init__,
+                               arg_names=('type',),
+                               category=PyGTKDeprecationWarning)
 
 Window = override(Window)
 __all__.append('Window')
 
 
 class Dialog(Gtk.Dialog, Container):
+    _old_arg_names = ('title', 'parent', 'flags', 'buttons', '_buttons_property')
+    _init = deprecated_init(Gtk.Dialog.__init__,
+                            arg_names=('title', 'transient_for', 'flags',
+                                       'add_buttons', 'buttons'),
+                            ignore=('flags', 'add_buttons'),
+                            deprecated_aliases={'transient_for': 'parent',
+                                                'buttons': '_buttons_property'},
+                            category=PyGTKDeprecationWarning)
+
+    def __init__(self, *args, **kwargs):
+
+        new_kwargs = kwargs.copy()
+        old_kwargs = dict(zip(self._old_arg_names, args))
+        old_kwargs.update(kwargs)
+
+        # Increment the warning stacklevel for sub-classes which implement their own __init__.
+        stacklevel = 2
+        if self.__class__ != Dialog and self.__class__.__init__ != Dialog.__init__:
+            stacklevel += 1
+
+        # buttons was overloaded by PyGtk but is needed for Gtk.MessageDialog
+        # as a pass through, so type check the argument and give a deprecation
+        # when it is not of type Gtk.ButtonsType
+        add_buttons = old_kwargs.get('buttons', None)
+        if add_buttons is not None and not isinstance(add_buttons, Gtk.ButtonsType):
+            warnings.warn('The "buttons" argument must be a Gtk.ButtonsType enum value. '
+                          'Please use the "add_buttons" method for adding buttons. '
+                          'See: https://wiki.gnome.org/PyGObject/InitializerDeprecations',
+                          PyGTKDeprecationWarning, stacklevel=stacklevel)
+            if 'buttons' in new_kwargs:
+                del new_kwargs['buttons']
+        else:
+            add_buttons = None
+
+        flags = old_kwargs.get('flags', 0)
+        if flags:
+            warnings.warn('The "flags" argument for dialog construction is deprecated. '
+                          'Please use initializer keywords: modal=True and/or destroy_with_parent=True. '
+                          'See: https://wiki.gnome.org/PyGObject/InitializerDeprecations',
+                          PyGTKDeprecationWarning, stacklevel=stacklevel)
+
+            if flags & Gtk.DialogFlags.MODAL:
+                new_kwargs['modal'] = True
+
+            if flags & Gtk.DialogFlags.DESTROY_WITH_PARENT:
+                new_kwargs['destroy_with_parent'] = True
+
+        self._init(*args, **new_kwargs)
 
-    def __init__(self,
-                 title=None,
-                 parent=None,
-                 flags=0,
-                 buttons=None,
-                 _buttons_property=None,
-                 **kwds):
-
-        # buttons is overloaded by PyGtk so we have to do the same here
-        # this breaks some subclasses of Dialog so add a _buttons_property
-        # keyword to work around this
-        if _buttons_property is not None:
-            kwds['buttons'] = _buttons_property
-
-        Gtk.Dialog.__init__(self, **kwds)
-        if title:
-            self.set_title(title)
-        if parent:
-            self.set_transient_for(parent)
-        if flags & Gtk.DialogFlags.MODAL:
-            self.set_modal(True)
-        if flags & Gtk.DialogFlags.DESTROY_WITH_PARENT:
-            self.set_destroy_with_parent(True)
-
-        # NO_SEPARATOR has been removed from Gtk 3
-        if hasattr(Gtk.DialogFlags, "NO_SEPARATOR") and (flags & Gtk.DialogFlags.NO_SEPARATOR):
-            self.set_has_separator(False)
-            import warnings
-            warnings.warn("Gtk.DialogFlags.NO_SEPARATOR has been depricated since Gtk+-3.0", 
PyGIDeprecationWarning)
-
-        if buttons is not None:
-            self.add_buttons(*buttons)
+        if add_buttons:
+            self.add_buttons(*add_buttons)
 
     action_area = property(lambda dialog: dialog.get_action_area())
     vbox = property(lambda dialog: dialog.get_content_area())
@@ -515,29 +534,12 @@ __all__.append('Dialog')
 
 
 class MessageDialog(Gtk.MessageDialog, Dialog):
-    def __init__(self,
-                 parent=None,
-                 flags=0,
-                 message_type=Gtk.MessageType.INFO,
-                 buttons=Gtk.ButtonsType.NONE,
-                 message_format=None,
-                 **kwds):
-
-        if message_format:
-            kwds['text'] = message_format
-
-        # type keyword is used for backwards compat with PyGTK
-        if 'type' in kwds:
-            import warnings
-            warnings.warn("The use of the keyword type as a parameter of the Gtk.MessageDialog constructor 
has been depricated. Please use message_type instead.", PyGIDeprecationWarning)
-            message_type = kwds.pop('type')
-
-        Gtk.MessageDialog.__init__(self,
-                                   _buttons_property=buttons,
-                                   message_type=message_type,
-                                   parent=parent,
-                                   flags=flags,
-                                   **kwds)
+    __init__ = deprecated_init(Gtk.MessageDialog.__init__,
+                               arg_names=('parent', 'flags', 'message_type',
+                                          'buttons', 'message_format'),
+                               deprecated_aliases={'text': 'message_format',
+                                                   'message_type': 'type'},
+                               category=PyGTKDeprecationWarning)
 
     def format_secondary_text(self, message_format):
         self.set_property('secondary-use-markup', False)
@@ -551,70 +553,49 @@ MessageDialog = override(MessageDialog)
 __all__.append('MessageDialog')
 
 
-class AboutDialog(Gtk.AboutDialog):
-    def __init__(self, **kwds):
-        Gtk.AboutDialog.__init__(self, **kwds)
-
-AboutDialog = override(AboutDialog)
-__all__.append('AboutDialog')
-
-
 class ColorSelectionDialog(Gtk.ColorSelectionDialog):
-    def __init__(self, title=None, **kwds):
-        Gtk.ColorSelectionDialog.__init__(self, title=title, **kwds)
+    __init__ = deprecated_init(Gtk.ColorSelectionDialog.__init__,
+                               arg_names=('title',),
+                               category=PyGTKDeprecationWarning)
 
 ColorSelectionDialog = override(ColorSelectionDialog)
 __all__.append('ColorSelectionDialog')
 
 
 class FileChooserDialog(Gtk.FileChooserDialog):
-    def __init__(self,
-                 title=None,
-                 parent=None,
-                 action=Gtk.FileChooserAction.OPEN,
-                 buttons=None,
-                 **kwds):
-        Gtk.FileChooserDialog.__init__(self,
-                                       action=action,
-                                       title=title,
-                                       parent=parent,
-                                       buttons=buttons,
-                                       **kwds)
+    __init__ = deprecated_init(Gtk.FileChooserDialog.__init__,
+                               arg_names=('title', 'parent', 'action', 'buttons'),
+                               category=PyGTKDeprecationWarning)
+
 FileChooserDialog = override(FileChooserDialog)
 __all__.append('FileChooserDialog')
 
 
 class FontSelectionDialog(Gtk.FontSelectionDialog):
-    def __init__(self, title=None, **kwds):
-        Gtk.FontSelectionDialog.__init__(self, title=title, **kwds)
+    __init__ = deprecated_init(Gtk.FontSelectionDialog.__init__,
+                               arg_names=('title',),
+                               category=PyGTKDeprecationWarning)
 
 FontSelectionDialog = override(FontSelectionDialog)
 __all__.append('FontSelectionDialog')
 
 
 class RecentChooserDialog(Gtk.RecentChooserDialog):
-    def __init__(self,
-                 title=None,
-                 parent=None,
-                 manager=None,
-                 buttons=None,
-                 **kwds):
-
-        Gtk.RecentChooserDialog.__init__(self,
-                                         recent_manager=manager,
-                                         title=title,
-                                         parent=parent,
-                                         buttons=buttons,
-                                         **kwds)
+    # Note, the "manager" keyword must work across the entire 3.x series because
+    # "recent_manager" is not backwards compatible with PyGObject versions prior to 3.10.
+    __init__ = deprecated_init(Gtk.RecentChooserDialog.__init__,
+                               arg_names=('title', 'parent', 'recent_manager', 'buttons'),
+                               deprecated_aliases={'recent_manager': 'manager'},
+                               category=PyGTKDeprecationWarning)
 
 RecentChooserDialog = override(RecentChooserDialog)
 __all__.append('RecentChooserDialog')
 
 
 class IconView(Gtk.IconView):
-
-    def __init__(self, model=None, **kwds):
-        Gtk.IconView.__init__(self, model=model, **kwds)
+    __init__ = deprecated_init(Gtk.IconView.__init__,
+                               arg_names=('model',),
+                               category=PyGTKDeprecationWarning)
 
     get_item_at_pos = strip_boolean_result(Gtk.IconView.get_item_at_pos)
     get_visible_range = strip_boolean_result(Gtk.IconView.get_visible_range)
@@ -625,9 +606,9 @@ __all__.append('IconView')
 
 
 class ToolButton(Gtk.ToolButton):
-
-    def __init__(self, stock_id=None, **kwds):
-        Gtk.ToolButton.__init__(self, stock_id=stock_id, **kwds)
+    __init__ = deprecated_init(Gtk.ToolButton.__init__,
+                               arg_names=('stock_id',),
+                               category=PyGTKDeprecationWarning)
 
 ToolButton = override(ToolButton)
 __all__.append('ToolButton')
@@ -919,8 +900,9 @@ __all__.append('TreeSortable')
 
 
 class TreeModelSort(Gtk.TreeModelSort):
-    def __init__(self, model, **kwds):
-        Gtk.TreeModelSort.__init__(self, model=model, **kwds)
+    __init__ = deprecated_init(Gtk.TreeModelSort.__init__,
+                               arg_names=('model',),
+                               category=PyGTKDeprecationWarning)
 
 TreeModelSort = override(TreeModelSort)
 __all__.append('TreeModelSort')
@@ -1256,11 +1238,9 @@ __all__.append('TreeStore')
 
 
 class TreeView(Gtk.TreeView, Container):
-
-    def __init__(self, model=None):
-        Gtk.TreeView.__init__(self)
-        if model:
-            self.set_model(model)
+    __init__ = deprecated_init(Gtk.TreeView.__init__,
+                               arg_names=('model',),
+                               category=PyGTKDeprecationWarning)
 
     get_path_at_pos = strip_boolean_result(Gtk.TreeView.get_path_at_pos)
     get_visible_range = strip_boolean_result(Gtk.TreeView.get_visible_range)
@@ -1356,66 +1336,79 @@ __all__.append('TreeSelection')
 
 
 class Button(Gtk.Button, Container):
-    def __init__(self, label=None, stock=None, use_stock=False, use_underline=False, **kwds):
-        if stock:
-            label = stock
-            use_stock = True
-            use_underline = True
-        Gtk.Button.__init__(self, label=label, use_stock=use_stock,
-                            use_underline=use_underline, **kwds)
+    _init = deprecated_init(Gtk.Button.__init__,
+                            arg_names=('label', 'stock', 'use_stock', 'use_underline'),
+                            ignore=('stock',),
+                            category=PyGTKDeprecationWarning,
+                            stacklevel=3)
+
+    def __init__(self, *args, **kwargs):
+        # Doubly deprecated initializer, the stock keyword is non-standard.
+        # Simply give a warning that stock items are deprecated even though
+        # we want to deprecate the non-standard keyword as well here from
+        # the overrides.
+        if 'stock' in kwargs and kwargs['stock']:
+            warnings.warn('Stock items are deprecated. '
+                          'Please use: Gtk.Button.new_with_mnemonic(label)',
+                          PyGTKDeprecationWarning, stacklevel=2)
+            new_kwargs = kwargs.copy()
+            new_kwargs['label'] = new_kwargs['stock']
+            new_kwargs['use_stock'] = True
+            new_kwargs['use_underline'] = True
+            del new_kwargs['stock']
+            Gtk.Button.__init__(self, **new_kwargs)
+        else:
+            self._init(*args, **kwargs)
+
 Button = override(Button)
 __all__.append('Button')
 
 
 class LinkButton(Gtk.LinkButton):
-    def __init__(self, uri, label=None, **kwds):
-        Gtk.LinkButton.__init__(self, uri=uri, label=label, **kwds)
+    __init__ = deprecated_init(Gtk.LinkButton.__init__,
+                               arg_names=('uri', 'label'),
+                               category=PyGTKDeprecationWarning)
 
 LinkButton = override(LinkButton)
 __all__.append('LinkButton')
 
 
 class Label(Gtk.Label):
-    def __init__(self, label=None, **kwds):
-        Gtk.Label.__init__(self, label=label, **kwds)
+    __init__ = deprecated_init(Gtk.Label.__init__,
+                               arg_names=('label',),
+                               category=PyGTKDeprecationWarning)
 
 Label = override(Label)
 __all__.append('Label')
 
 
 class Adjustment(Gtk.Adjustment):
-    def __init__(self, *args, **kwds):
-        arg_names = ('value', 'lower', 'upper',
-                     'step_increment', 'page_increment', 'page_size')
-        new_args = dict(zip(arg_names, args))
-        new_args.update(kwds)
-
-        # PyGTK compatiblity
-        if 'page_incr' in new_args:
-            new_args['page_increment'] = new_args.pop('page_incr')
-        if 'step_incr' in new_args:
-            new_args['step_increment'] = new_args.pop('step_incr')
-        Gtk.Adjustment.__init__(self, **new_args)
+    _init = deprecated_init(Gtk.Adjustment.__init__,
+                            arg_names=('value', 'lower', 'upper',
+                                       'step_increment', 'page_increment', 'page_size'),
+                            deprecated_aliases={'page_increment': 'page_incr',
+                                                'step_increment': 'step_incr'},
+                            category=PyGTKDeprecationWarning,
+                            stacklevel=3)
+
+    def __init__(self, *args, **kwargs):
+        self._init(*args, **kwargs)
 
         # The value property is set between lower and (upper - page_size).
         # Just in case lower, upper or page_size was still 0 when value
         # was set, we set it again here.
-        if 'value' in new_args:
-            self.set_value(new_args['value'])
+        if 'value' in kwargs:
+            self.set_value(kwargs['value'])
 
 Adjustment = override(Adjustment)
 __all__.append('Adjustment')
 
 
 class Table(Gtk.Table, Container):
-    def __init__(self, rows=1, columns=1, homogeneous=False, **kwds):
-        if 'n_rows' in kwds:
-            rows = kwds.pop('n_rows')
-
-        if 'n_columns' in kwds:
-            columns = kwds.pop('n_columns')
-
-        Gtk.Table.__init__(self, n_rows=rows, n_columns=columns, homogeneous=homogeneous, **kwds)
+    __init__ = deprecated_init(Gtk.Table.__init__,
+                               arg_names=('n_rows', 'n_columns', 'homogeneous'),
+                               deprecated_aliases={'n_rows': 'rows', 'n_columns': 'columns'},
+                               category=PyGTKDeprecationWarning)
 
     def attach(self, child, left_attach, right_attach, top_attach, bottom_attach, 
xoptions=Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, yoptions=Gtk.AttachOptions.EXPAND | 
Gtk.AttachOptions.FILL, xpadding=0, ypadding=0):
         Gtk.Table.attach(self, child, left_attach, right_attach, top_attach, bottom_attach, xoptions, 
yoptions, xpadding, ypadding)
@@ -1425,24 +1418,27 @@ __all__.append('Table')
 
 
 class ScrolledWindow(Gtk.ScrolledWindow):
-    def __init__(self, hadjustment=None, vadjustment=None, **kwds):
-        Gtk.ScrolledWindow.__init__(self, hadjustment=hadjustment, vadjustment=vadjustment, **kwds)
+    __init__ = deprecated_init(Gtk.ScrolledWindow.__init__,
+                               arg_names=('hadjustment', 'vadjustment'),
+                               category=PyGTKDeprecationWarning)
 
 ScrolledWindow = override(ScrolledWindow)
 __all__.append('ScrolledWindow')
 
 
 class HScrollbar(Gtk.HScrollbar):
-    def __init__(self, adjustment=None, **kwds):
-        Gtk.HScrollbar.__init__(self, adjustment=adjustment, **kwds)
+    __init__ = deprecated_init(Gtk.HScrollbar.__init__,
+                               arg_names=('adjustment',),
+                               category=PyGTKDeprecationWarning)
 
 HScrollbar = override(HScrollbar)
 __all__.append('HScrollbar')
 
 
 class VScrollbar(Gtk.VScrollbar):
-    def __init__(self, adjustment=None, **kwds):
-        Gtk.VScrollbar.__init__(self, adjustment=adjustment, **kwds)
+    __init__ = deprecated_init(Gtk.VScrollbar.__init__,
+                               arg_names=('adjustment',),
+                               category=PyGTKDeprecationWarning)
 
 VScrollbar = override(VScrollbar)
 __all__.append('VScrollbar')
@@ -1460,10 +1456,9 @@ __all__.append('Paned')
 
 
 class Arrow(Gtk.Arrow):
-    def __init__(self, arrow_type, shadow_type, **kwds):
-        Gtk.Arrow.__init__(self, arrow_type=arrow_type,
-                           shadow_type=shadow_type,
-                           **kwds)
+    __init__ = deprecated_init(Gtk.Arrow.__init__,
+                               arg_names=('arrow_type', 'shadow_type'),
+                               category=PyGTKDeprecationWarning)
 
 Arrow = override(Arrow)
 __all__.append('Arrow')
@@ -1472,6 +1467,9 @@ __all__.append('Arrow')
 class IconSet(Gtk.IconSet):
     def __new__(cls, pixbuf=None):
         if pixbuf is not None:
+            warnings.warn('Gtk.IconSet(pixbuf) has been deprecated. Please use: '
+                          'Gtk.IconSet.new_from_pixbuf(pixbuf)',
+                          PyGTKDeprecationWarning, stacklevel=2)
             iconset = Gtk.IconSet.new_from_pixbuf(pixbuf)
         else:
             iconset = Gtk.IconSet.__new__(cls)
@@ -1482,10 +1480,9 @@ __all__.append('IconSet')
 
 
 class Viewport(Gtk.Viewport):
-    def __init__(self, hadjustment=None, vadjustment=None, **kwds):
-        Gtk.Viewport.__init__(self, hadjustment=hadjustment,
-                              vadjustment=vadjustment,
-                              **kwds)
+    __init__ = deprecated_init(Gtk.Viewport.__init__,
+                               arg_names=('hadjustment', 'vadjustment'),
+                               category=PyGTKDeprecationWarning)
 
 Viewport = override(Viewport)
 __all__.append('Viewport')
diff --git a/gi/overrides/__init__.py b/gi/overrides/__init__.py
index 8aa9731..3406622 100644
--- a/gi/overrides/__init__.py
+++ b/gi/overrides/__init__.py
@@ -90,6 +90,94 @@ def deprecated(fn, replacement):
     return wrapped
 
 
+def deprecated_init(super_init_func, arg_names, ignore=tuple(),
+                    deprecated_aliases={}, deprecated_defaults={},
+                    category=PyGIDeprecationWarning,
+                    stacklevel=2):
+    '''Wrapper for deprecating GObject based __init__ methods which specify defaults
+    already available or non-standard defaults.
+
+    :Parameters:
+        super_init_func : callable
+            Initializer to wrap.
+        arg_names : list
+            Ordered argument name list.
+        ignore : list
+            List of argument names to ignore when calling the wrapped function.
+            This is useful for function which take a non-standard keyword that
+            is munged elsewhere.
+        deprecated_aliases : dict
+            Dictionary mapping a keyword alias to the actual g_object_newv
+            keyword.
+        deprecated_defaults : dict
+            Dictionary of non-standard defaults that will be used when the
+            keyword is not explicitly passed.
+        category : Exception
+            Exception category of the error.
+        stacklevel : int
+            Stack level for the deprecation passed on to warnings.warn
+
+    :Returns:
+        Wrapped version of super_init_func which gives a deprecation
+        warning when non-keyword args or aliases are used.
+    '''
+    # We use a list of argument names to maintain order of the arguments
+    # being deprecated. This allows calls with positional arguments to
+    # continue working but with a deprecation message.
+    def new_init(self, *args, **kwargs):
+        '''Initializer for a GObject based classes with support for property
+        sets through the use of explicit keyword arguments.
+        '''
+        # Print warnings for calls with positional arguments.
+        if args:
+            warnings.warn('Using positional arguments with the GObject constructor has been deprecated. '
+                          'Please specify keywords for %s or use a class specific constructor. '
+                          'See: https://wiki.gnome.org/PyGObject/InitializerDeprecations' %
+                          ', '.join(arg_names[:len(args)]),
+                          category, stacklevel=stacklevel)
+            new_kwargs = dict(zip(arg_names, args))
+        else:
+            new_kwargs = {}
+        new_kwargs.update(kwargs)
+
+        # Print warnings for alias usage and transfer them into the new key.
+        aliases_used = []
+        for key, alias in deprecated_aliases.items():
+            if alias in new_kwargs:
+                new_kwargs[key] = new_kwargs.pop(alias)
+                aliases_used.append(key)
+
+        if aliases_used:
+            warnings.warn('The keyword(s) "%s" have been deprecated in favor of "%s" respectively. '
+                          'See: https://wiki.gnome.org/PyGObject/InitializerDeprecations' %
+                          (', '.join(deprecated_aliases[k] for k in sorted(aliases_used)),
+                           ', '.join(sorted(aliases_used))),
+                          category, stacklevel=stacklevel)
+
+        # Print warnings for defaults different than what is already provided by the property
+        defaults_used = []
+        for key, value in deprecated_defaults.items():
+            if key not in new_kwargs:
+                new_kwargs[key] = deprecated_defaults[key]
+                defaults_used.append(key)
+
+        if defaults_used:
+            warnings.warn('Initializer is relying on deprecated non-standard '
+                          'defaults. Please update to explicitly use: %s '
+                          'See: https://wiki.gnome.org/PyGObject/InitializerDeprecations' %
+                          ', '.join('%s=%s' % (k, deprecated_defaults[k]) for k in sorted(defaults_used)),
+                          category, stacklevel=stacklevel)
+
+        # Remove keywords that should be ignored.
+        for key in ignore:
+            if key in new_kwargs:
+                new_kwargs.pop(key)
+
+        return super_init_func(self, **new_kwargs)
+
+    return new_init
+
+
 def strip_boolean_result(method, exc_type=None, exc_str=None, fail_ret=None):
     '''Translate method's return value for stripping off success flag.
 
diff --git a/tests/test_gi.py b/tests/test_gi.py
index 026ac55..f7a7bc6 100644
--- a/tests/test_gi.py
+++ b/tests/test_gi.py
@@ -16,6 +16,8 @@ import warnings
 from io import StringIO, BytesIO
 
 import gi
+import gi.overrides
+from gi import PyGIDeprecationWarning
 from gi.repository import GObject, GLib, Gio
 
 from gi.repository import GIMarshallingTests
@@ -3000,3 +3002,76 @@ class TestDeprecation(unittest.TestCase):
             warnings.simplefilter('always')
             d.set_time(1)
             self.assertTrue(issubclass(warn[0].category, DeprecationWarning))
+
+    def test_deprecated_init_no_keywords(self):
+        def init(self, **kwargs):
+            self.assertDictEqual(kwargs, {'a': 1, 'b': 2, 'c': 3})
+
+        fn = gi.overrides.deprecated_init(init, arg_names=('a', 'b', 'c'))
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+            fn(self, 1, 2, 3)
+            self.assertEqual(len(warn), 1)
+            self.assertTrue(issubclass(warn[0].category, PyGIDeprecationWarning))
+            self.assertRegexpMatches(str(warn[0].message),
+                                     '.*keywords.*a, b, c.*')
+
+    def test_deprecated_init_no_keywords_out_of_order(self):
+        def init(self, **kwargs):
+            self.assertDictEqual(kwargs, {'a': 1, 'b': 2, 'c': 3})
+
+        fn = gi.overrides.deprecated_init(init, arg_names=('b', 'a', 'c'))
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+            fn(self, 2, 1, 3)
+            self.assertEqual(len(warn), 1)
+            self.assertTrue(issubclass(warn[0].category, PyGIDeprecationWarning))
+            self.assertRegexpMatches(str(warn[0].message),
+                                     '.*keywords.*b, a, c.*')
+
+    def test_deprecated_init_ignored_keyword(self):
+        def init(self, **kwargs):
+            self.assertDictEqual(kwargs, {'a': 1, 'c': 3})
+
+        fn = gi.overrides.deprecated_init(init,
+                                          arg_names=('a', 'b', 'c'),
+                                          ignore=('b',))
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+            fn(self, 1, 2, 3)
+            self.assertEqual(len(warn), 1)
+            self.assertTrue(issubclass(warn[0].category, PyGIDeprecationWarning))
+            self.assertRegexpMatches(str(warn[0].message),
+                                     '.*keywords.*a, b, c.*')
+
+    def test_deprecated_init_with_aliases(self):
+        def init(self, **kwargs):
+            self.assertDictEqual(kwargs, {'a': 1, 'b': 2, 'c': 3})
+
+        fn = gi.overrides.deprecated_init(init,
+                                          arg_names=('a', 'b', 'c'),
+                                          deprecated_aliases={'b': 'bb', 'c': 'cc'})
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+
+            fn(self, a=1, bb=2, cc=3)
+            self.assertEqual(len(warn), 1)
+            self.assertTrue(issubclass(warn[0].category, PyGIDeprecationWarning))
+            self.assertRegexpMatches(str(warn[0].message),
+                                     '.*keyword.*"bb, cc".*deprecated.*"b, c" respectively')
+
+    def test_deprecated_init_with_defaults(self):
+        def init(self, **kwargs):
+            self.assertDictEqual(kwargs, {'a': 1, 'b': 2, 'c': 3})
+
+        fn = gi.overrides.deprecated_init(init,
+                                          arg_names=('a', 'b', 'c'),
+                                          deprecated_defaults={'b': 2, 'c': 3})
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+            fn(self, a=1)
+            self.assertEqual(len(warn), 1)
+            self.assertTrue(issubclass(warn[0].category, PyGIDeprecationWarning))
+            self.assertRegexpMatches(str(warn[0].message),
+                                     '.*relying on deprecated non-standard defaults.*'
+                                     'explicitly use: b=2, c=3')
diff --git a/tests/test_overrides_gtk.py b/tests/test_overrides_gtk.py
index 3cfadf5..4ace879 100644
--- a/tests/test_overrides_gtk.py
+++ b/tests/test_overrides_gtk.py
@@ -6,6 +6,7 @@ import contextlib
 import unittest
 import time
 import sys
+import warnings
 
 from compathelper import _unicode, _bytes
 
@@ -16,8 +17,10 @@ from gi.repository import GLib, GObject
 try:
     from gi.repository import GdkPixbuf, Gdk, Gtk
     Gtk  # pyflakes
+    PyGTKDeprecationWarning = Gtk.PyGTKDeprecationWarning
 except ImportError:
     Gtk = None
+    PyGTKDeprecationWarning = None
 
 
 @contextlib.contextmanager
@@ -73,7 +76,6 @@ class TestGtk(unittest.TestCase):
 
     def test_actions(self):
         self.assertEqual(Gtk.Action, gi.overrides.Gtk.Action)
-        self.assertRaises(TypeError, Gtk.Action)
         action = Gtk.Action(name="test", label="Test", tooltip="Test Action", stock_id=Gtk.STOCK_COPY)
         self.assertEqual(action.get_name(), "test")
         self.assertEqual(action.get_label(), "Test")
@@ -81,7 +83,6 @@ class TestGtk(unittest.TestCase):
         self.assertEqual(action.get_stock_id(), Gtk.STOCK_COPY)
 
         self.assertEqual(Gtk.RadioAction, gi.overrides.Gtk.RadioAction)
-        self.assertRaises(TypeError, Gtk.RadioAction)
         action = Gtk.RadioAction(name="test", label="Test", tooltip="Test Action", stock_id=Gtk.STOCK_COPY, 
value=1)
         self.assertEqual(action.get_name(), "test")
         self.assertEqual(action.get_label(), "Test")
@@ -91,7 +92,6 @@ class TestGtk(unittest.TestCase):
 
     def test_actiongroup(self):
         self.assertEqual(Gtk.ActionGroup, gi.overrides.Gtk.ActionGroup)
-        self.assertRaises(TypeError, Gtk.ActionGroup)
 
         action_group = Gtk.ActionGroup(name='TestActionGroup')
         callback_data = "callback data"
@@ -165,10 +165,6 @@ class TestGtk(unittest.TestCase):
         w = Gtk.Window(type=Gtk.WindowType.POPUP)
         self.assertEqual(w.get_property('type'), Gtk.WindowType.POPUP)
 
-        # pygtk compatible positional argument
-        w = Gtk.Window(Gtk.WindowType.POPUP)
-        self.assertEqual(w.get_property('type'), Gtk.WindowType.POPUP)
-
         class TestWindow(Gtk.Window):
             __gtype_name__ = "TestWindow"
 
@@ -194,8 +190,6 @@ class TestGtk(unittest.TestCase):
 
     def test_dialog_classes(self):
         self.assertEqual(Gtk.Dialog, gi.overrides.Gtk.Dialog)
-        self.assertEqual(Gtk.AboutDialog, gi.overrides.Gtk.AboutDialog)
-        self.assertEqual(Gtk.MessageDialog, gi.overrides.Gtk.MessageDialog)
         self.assertEqual(Gtk.ColorSelectionDialog, gi.overrides.Gtk.ColorSelectionDialog)
         self.assertEqual(Gtk.FileChooserDialog, gi.overrides.Gtk.FileChooserDialog)
         self.assertEqual(Gtk.FontSelectionDialog, gi.overrides.Gtk.FontSelectionDialog)
@@ -208,9 +202,65 @@ class TestGtk(unittest.TestCase):
         self.assertEqual('Foo', dialog.get_title())
         self.assertTrue(dialog.get_modal())
 
+    def test_dialog_deprecations(self):
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+            dialog = Gtk.Dialog(title='Foo', flags=Gtk.DialogFlags.MODAL)
+            self.assertTrue(dialog.get_modal())
+            self.assertEqual(len(warn), 1)
+            self.assertTrue(issubclass(warn[0].category, PyGTKDeprecationWarning))
+            self.assertRegexpMatches(str(warn[0].message),
+                                     '.*flags.*modal.*')
+
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+            dialog = Gtk.Dialog(title='Foo', flags=Gtk.DialogFlags.DESTROY_WITH_PARENT)
+            self.assertTrue(dialog.get_destroy_with_parent())
+            self.assertEqual(len(warn), 1)
+            self.assertTrue(issubclass(warn[0].category, PyGTKDeprecationWarning))
+            self.assertRegexpMatches(str(warn[0].message),
+                                     '.*flags.*destroy_with_parent.*')
+
+    def test_dialog_deprecation_stacklevels(self):
+        # Test warning levels are setup to give the correct filename for
+        # deprecations in different classes in the inheritance hierarchy.
+
+        # Base class
+        self.assertEqual(Gtk.Dialog, gi.overrides.Gtk.Dialog)
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+            Gtk.Dialog(flags=Gtk.DialogFlags.MODAL)
+            self.assertEqual(len(warn), 1)
+            self.assertRegexpMatches(warn[0].filename, '.*test_overrides_gtk.*')
+
+        # Validate overridden base with overridden sub-class.
+        self.assertEqual(Gtk.MessageDialog, gi.overrides.Gtk.MessageDialog)
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+            Gtk.MessageDialog(flags=Gtk.DialogFlags.MODAL)
+            self.assertEqual(len(warn), 1)
+            self.assertRegexpMatches(warn[0].filename, '.*test_overrides_gtk.*')
+
+        # Validate overridden base with non-overridden sub-class.
+        self.assertEqual(Gtk.AboutDialog, gi.repository.Gtk.AboutDialog)
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+            Gtk.AboutDialog(flags=Gtk.DialogFlags.MODAL)
+            self.assertEqual(len(warn), 1)
+            self.assertRegexpMatches(warn[0].filename, '.*test_overrides_gtk.*')
+
     def test_dialog_add_buttons(self):
-        dialog = Gtk.Dialog(title='Foo', modal=True,
-                            buttons=('test-button1', 1))
+        # The overloaded "buttons" keyword gives a warning when attempting
+        # to use it for adding buttons as was available in PyGTK.
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+            dialog = Gtk.Dialog(title='Foo', modal=True,
+                                buttons=('test-button1', 1))
+            self.assertEqual(len(warn), 1)
+            self.assertTrue(issubclass(warn[0].category, PyGTKDeprecationWarning))
+            self.assertRegexpMatches(str(warn[0].message),
+                                     '.*ButtonsType.*add_buttons.*')
+
         dialog.add_buttons('test-button2', 2, Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE)
         button = dialog.get_widget_for_response(1)
         self.assertEqual('test-button1', button.get_label())
@@ -261,21 +311,13 @@ class TestGtk(unittest.TestCase):
             GLib.LogLevelFlags.LEVEL_CRITICAL | GLib.LogLevelFlags.LEVEL_ERROR)
         try:
             dialog = Gtk.FileChooserDialog(title='file chooser dialog test',
-                                           buttons=('test-button1', 1),
                                            action=Gtk.FileChooserAction.SAVE)
         finally:
             GLib.log_set_always_fatal(old_mask)
         self.assertTrue(isinstance(dialog, Gtk.Dialog))
         self.assertTrue(isinstance(dialog, Gtk.Window))
-
-        dialog.add_buttons('test-button2', 2, Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE)
         self.assertEqual('file chooser dialog test', dialog.get_title())
-        button = dialog.get_widget_for_response(1)
-        self.assertEqual('test-button1', button.get_label())
-        button = dialog.get_widget_for_response(2)
-        self.assertEqual('test-button2', button.get_label())
-        button = dialog.get_widget_for_response(Gtk.ResponseType.CLOSE)
-        self.assertEqual(Gtk.STOCK_CLOSE, button.get_label())
+
         action = dialog.get_property('action')
         self.assertEqual(Gtk.FileChooserAction.SAVE, action)
 
@@ -300,19 +342,10 @@ class TestGtk(unittest.TestCase):
     def test_recent_chooser_dialog(self):
         test_manager = Gtk.RecentManager()
         dialog = Gtk.RecentChooserDialog(title='recent chooser dialog test',
-                                         buttons=('test-button1', 1),
-                                         manager=test_manager)
+                                         recent_manager=test_manager)
         self.assertTrue(isinstance(dialog, Gtk.Dialog))
         self.assertTrue(isinstance(dialog, Gtk.Window))
-
-        dialog.add_buttons('test-button2', 2, Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE)
         self.assertEqual('recent chooser dialog test', dialog.get_title())
-        button = dialog.get_widget_for_response(1)
-        self.assertEqual('test-button1', button.get_label())
-        button = dialog.get_widget_for_response(2)
-        self.assertEqual('test-button2', button.get_label())
-        button = dialog.get_widget_for_response(Gtk.ResponseType.CLOSE)
-        self.assertEqual(Gtk.STOCK_CLOSE, button.get_label())
 
     class TestClass(GObject.GObject):
         __gtype_name__ = "GIOverrideTreeAPITest"
@@ -347,7 +380,6 @@ class TestGtk(unittest.TestCase):
         self.assertTrue(button.get_use_underline())
 
         # test Gtk.LinkButton
-        self.assertRaises(TypeError, Gtk.LinkButton)
         button = Gtk.LinkButton(uri='http://www.Gtk.org', label='Gtk')
         self.assertTrue(isinstance(button, Gtk.Button))
         self.assertTrue(isinstance(button, Gtk.Container))
@@ -761,7 +793,6 @@ class TestBuilder(unittest.TestCase):
 class TestTreeModel(unittest.TestCase):
     def test_tree_model_sort(self):
         self.assertEqual(Gtk.TreeModelSort, gi.overrides.Gtk.TreeModelSort)
-        self.assertRaises(TypeError, Gtk.TreeModelSort)
         model = Gtk.TreeStore(int, bool)
         model_sort = Gtk.TreeModelSort(model=model)
         self.assertEqual(model_sort.get_model(), model)



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]