Commit 86a37d67 authored by Simon Feltman's avatar Simon Feltman

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
parent d2e9be8e
......@@ -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()
......
This diff is collapsed.
......@@ -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.
......
......@@ -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')
......@@ -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)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment