diff options
-rw-r--r-- | gi/overrides/GLib.py | 44 | ||||
-rw-r--r-- | gi/overrides/GObject.py | 90 | ||||
-rw-r--r-- | gi/overrides/__init__.py | 74 | ||||
-rw-r--r-- | tests/test_gi.py | 70 |
4 files changed, 221 insertions, 57 deletions
diff --git a/gi/overrides/GLib.py b/gi/overrides/GLib.py index ce15da1f..c1f1691a 100644 --- a/gi/overrides/GLib.py +++ b/gi/overrides/GLib.py @@ -26,7 +26,7 @@ import sys from ..module import get_introspection_module from .._gi import (variant_type_from_string, source_new, source_set_callback, io_channel_read) -from ..overrides import override, deprecated +from ..overrides import override, deprecated, deprecated_attr from gi import PyGIDeprecationWarning, version_info GLib = get_introspection_module('GLib') @@ -481,8 +481,10 @@ __all__.append('markup_escape_text') # backwards compatible names from old static bindings for n in ['DESKTOP', 'DOCUMENTS', 'DOWNLOAD', 'MUSIC', 'PICTURES', 'PUBLIC_SHARE', 'TEMPLATES', 'VIDEOS']: - globals()['USER_DIRECTORY_' + n] = getattr(GLib.UserDirectory, 'DIRECTORY_' + n) - __all__.append('USER_DIRECTORY_' + n) + attr = 'USER_DIRECTORY_' + n + deprecated_attr("GLib", attr, "GLib.UserDirectory.DIRECTORY_" + n) + globals()[attr] = getattr(GLib.UserDirectory, 'DIRECTORY_' + n) + __all__.append(attr) for n in ['ERR', 'HUP', 'IN', 'NVAL', 'OUT', 'PRI']: globals()['IO_' + n] = getattr(GLib.IOCondition, n) @@ -490,30 +492,42 @@ for n in ['ERR', 'HUP', 'IN', 'NVAL', 'OUT', 'PRI']: for n in ['APPEND', 'GET_MASK', 'IS_READABLE', 'IS_SEEKABLE', 'MASK', 'NONBLOCK', 'SET_MASK']: - globals()['IO_FLAG_' + n] = getattr(GLib.IOFlags, n) - __all__.append('IO_FLAG_' + n) + attr = 'IO_FLAG_' + n + deprecated_attr("GLib", attr, "GLib.IOFlags." + n) + globals()[attr] = getattr(GLib.IOFlags, n) + __all__.append(attr) + # spelling for the win IO_FLAG_IS_WRITEABLE = GLib.IOFlags.IS_WRITABLE +deprecated_attr("GLib", "IO_FLAG_IS_WRITEABLE", "GLib.IOFlags.IS_WRITABLE") __all__.append('IO_FLAG_IS_WRITEABLE') for n in ['AGAIN', 'EOF', 'ERROR', 'NORMAL']: - globals()['IO_STATUS_' + n] = getattr(GLib.IOStatus, n) - __all__.append('IO_STATUS_' + n) + attr = 'IO_STATUS_' + n + globals()[attr] = getattr(GLib.IOStatus, n) + deprecated_attr("GLib", attr, "GLib.IOStatus." + n) + __all__.append(attr) for n in ['CHILD_INHERITS_STDIN', 'DO_NOT_REAP_CHILD', 'FILE_AND_ARGV_ZERO', 'LEAVE_DESCRIPTORS_OPEN', 'SEARCH_PATH', 'STDERR_TO_DEV_NULL', 'STDOUT_TO_DEV_NULL']: - globals()['SPAWN_' + n] = getattr(GLib.SpawnFlags, n) - __all__.append('SPAWN_' + n) + attr = 'SPAWN_' + n + globals()[attr] = getattr(GLib.SpawnFlags, n) + deprecated_attr("GLib", attr, "GLib.SpawnFlags." + n) + __all__.append(attr) for n in ['HIDDEN', 'IN_MAIN', 'REVERSE', 'NO_ARG', 'FILENAME', 'OPTIONAL_ARG', 'NOALIAS']: - globals()['OPTION_FLAG_' + n] = getattr(GLib.OptionFlags, n) - __all__.append('OPTION_FLAG_' + n) + attr = 'OPTION_FLAG_' + n + globals()[attr] = getattr(GLib.OptionFlags, n) + deprecated_attr("GLib", attr, "GLib.OptionFlags." + n) + __all__.append(attr) for n in ['UNKNOWN_OPTION', 'BAD_VALUE', 'FAILED']: - globals()['OPTION_ERROR_' + n] = getattr(GLib.OptionError, n) - __all__.append('OPTION_ERROR_' + n) + attr = 'OPTION_ERROR_' + n + deprecated_attr("GLib", attr, "GLib.OptionError." + n) + globals()[attr] = getattr(GLib.OptionError, n) + __all__.append(attr) class MainLoop(GLib.MainLoop): @@ -901,5 +915,9 @@ if not hasattr(GLib, 'unix_signal_add_full'): # obsolete constants for backwards compatibility glib_version = (GLib.MAJOR_VERSION, GLib.MINOR_VERSION, GLib.MICRO_VERSION) __all__.append('glib_version') +deprecated_attr("GLib", "glib_version", + "(GLib.MAJOR_VERSION, GLib.MINOR_VERSION, GLib.MICRO_VERSION)") + pyglib_version = version_info __all__.append('pyglib_version') +deprecated_attr("GLib", "pyglib_version", "gi.version_info") diff --git a/gi/overrides/GObject.py b/gi/overrides/GObject.py index e922ac09..d0d52252 100644 --- a/gi/overrides/GObject.py +++ b/gi/overrides/GObject.py @@ -27,7 +27,7 @@ from collections import namedtuple import gi.overrides import gi.module -from gi.overrides import override +from gi.overrides import override, deprecated_attr from gi.repository import GLib from gi import PyGIDeprecationWarning @@ -56,10 +56,11 @@ for name in ['markup_escape_text', 'get_application_name', 'idle_add', 'timeout_add', 'timeout_add_seconds', 'io_add_watch', 'child_watch_add', 'get_current_time', 'spawn_async']: - globals()[name] = gi.overrides.deprecated(getattr(GLib, name), 'GLib.' + name) + globals()[name] = getattr(GLib, name) + deprecated_attr("GObject", name, "GLib." + name) __all__.append(name) -# constants are also deprecated, but cannot mark them as such +# deprecated constants for name in ['PRIORITY_DEFAULT', 'PRIORITY_DEFAULT_IDLE', 'PRIORITY_HIGH', 'PRIORITY_HIGH_IDLE', 'PRIORITY_LOW', 'IO_IN', 'IO_OUT', 'IO_PRI', 'IO_ERR', 'IO_HUP', 'IO_NVAL', @@ -77,25 +78,21 @@ for name in ['PRIORITY_DEFAULT', 'PRIORITY_DEFAULT_IDLE', 'PRIORITY_HIGH', 'OPTION_FLAG_NOALIAS', 'OPTION_ERROR_UNKNOWN_OPTION', 'OPTION_ERROR_BAD_VALUE', 'OPTION_ERROR_FAILED', 'OPTION_REMAINING', 'glib_version']: - globals()[name] = getattr(GLib, name) + with warnings.catch_warnings(): + # TODO: this uses deprecated Glib attributes, silence for now + warnings.simplefilter('ignore', PyGIDeprecationWarning) + globals()[name] = getattr(GLib, name) + deprecated_attr("GObject", name, "GLib." + name) __all__.append(name) -G_MININT8 = GLib.MININT8 -G_MAXINT8 = GLib.MAXINT8 -G_MAXUINT8 = GLib.MAXUINT8 -G_MININT16 = GLib.MININT16 -G_MAXINT16 = GLib.MAXINT16 -G_MAXUINT16 = GLib.MAXUINT16 -G_MININT32 = GLib.MININT32 -G_MAXINT32 = GLib.MAXINT32 -G_MAXUINT32 = GLib.MAXUINT32 -G_MININT64 = GLib.MININT64 -G_MAXINT64 = GLib.MAXINT64 -G_MAXUINT64 = GLib.MAXUINT64 -__all__ += ['G_MININT8', 'G_MAXINT8', 'G_MAXUINT8', 'G_MININT16', - 'G_MAXINT16', 'G_MAXUINT16', 'G_MININT32', 'G_MAXINT32', - 'G_MAXUINT32', 'G_MININT64', 'G_MAXINT64', 'G_MAXUINT64'] +for name in ['G_MININT8', 'G_MAXINT8', 'G_MAXUINT8', 'G_MININT16', + 'G_MAXINT16', 'G_MAXUINT16', 'G_MININT32', 'G_MAXINT32', + 'G_MAXUINT32', 'G_MININT64', 'G_MAXINT64', 'G_MAXUINT64']: + new_name = name.split("_", 1)[-1] + globals()[name] = getattr(GLib, new_name) + deprecated_attr("GObject", name, "GLib." + new_name) + __all__.append(name) # these are not currently exported in GLib gir, presumably because they are # platform dependent; so get them from our static bindings @@ -145,38 +142,44 @@ __all__ += ['TYPE_INVALID', 'TYPE_NONE', 'TYPE_INTERFACE', 'TYPE_CHAR', # Deprecated, use GLib directly -Pid = GLib.Pid -GError = GLib.GError -OptionGroup = GLib.OptionGroup -OptionContext = GLib.OptionContext -__all__ += ['Pid', 'GError', 'OptionGroup', 'OptionContext'] +for name in ['Pid', 'GError', 'OptionGroup', 'OptionContext']: + globals()[name] = getattr(GLib, name) + deprecated_attr("GObject", name, "GLib." + name) + __all__.append(name) # Deprecated, use: GObject.ParamFlags.* directly -PARAM_CONSTRUCT = GObjectModule.ParamFlags.CONSTRUCT -PARAM_CONSTRUCT_ONLY = GObjectModule.ParamFlags.CONSTRUCT_ONLY -PARAM_LAX_VALIDATION = GObjectModule.ParamFlags.LAX_VALIDATION -PARAM_READABLE = GObjectModule.ParamFlags.READABLE -PARAM_WRITABLE = GObjectModule.ParamFlags.WRITABLE +for name in ['PARAM_CONSTRUCT', 'PARAM_CONSTRUCT_ONLY', 'PARAM_LAX_VALIDATION', + 'PARAM_READABLE', 'PARAM_WRITABLE']: + new_name = name.split("_", 1)[-1] + globals()[name] = getattr(GObjectModule.ParamFlags, new_name) + deprecated_attr("GObject", name, "GObject.ParamFlags." + new_name) + __all__.append(name) + # PARAM_READWRITE should come from the gi module but cannot due to: # https://bugzilla.gnome.org/show_bug.cgi?id=687615 -PARAM_READWRITE = PARAM_READABLE | PARAM_WRITABLE -__all__ += ['PARAM_CONSTRUCT', 'PARAM_CONSTRUCT_ONLY', 'PARAM_LAX_VALIDATION', - 'PARAM_READABLE', 'PARAM_WRITABLE', 'PARAM_READWRITE'] +PARAM_READWRITE = GObjectModule.ParamFlags.READABLE | \ + GObjectModule.ParamFlags.WRITABLE +__all__.append("PARAM_READWRITE") +# READWRITE is part of ParamFlags since glib 2.42. Only mark PARAM_READWRITE as +# deprecated in case ParamFlags.READWRITE is available. Also include the glib +# version in the warning so it's clear that this needs a newer glib, unlike +# the other ParamFlags related deprecations. +# https://bugzilla.gnome.org/show_bug.cgi?id=726037 +if hasattr(GObjectModule.ParamFlags, "READWRITE"): + deprecated_attr("GObject", "PARAM_READWRITE", + "GObject.ParamFlags.READWRITE (glib 2.42+)") -# Deprecated, use: GObject.SignalFlags.* directly -SIGNAL_ACTION = GObjectModule.SignalFlags.ACTION -SIGNAL_DETAILED = GObjectModule.SignalFlags.DETAILED -SIGNAL_NO_HOOKS = GObjectModule.SignalFlags.NO_HOOKS -SIGNAL_NO_RECURSE = GObjectModule.SignalFlags.NO_RECURSE -SIGNAL_RUN_CLEANUP = GObjectModule.SignalFlags.RUN_CLEANUP -SIGNAL_RUN_FIRST = GObjectModule.SignalFlags.RUN_FIRST -SIGNAL_RUN_LAST = GObjectModule.SignalFlags.RUN_LAST -__all__ += ['SIGNAL_ACTION', 'SIGNAL_DETAILED', 'SIGNAL_NO_HOOKS', - 'SIGNAL_NO_RECURSE', 'SIGNAL_RUN_CLEANUP', 'SIGNAL_RUN_FIRST', - 'SIGNAL_RUN_LAST'] +# Deprecated, use: GObject.SignalFlags.* directly +for name in ['SIGNAL_ACTION', 'SIGNAL_DETAILED', 'SIGNAL_NO_HOOKS', + 'SIGNAL_NO_RECURSE', 'SIGNAL_RUN_CLEANUP', 'SIGNAL_RUN_FIRST', + 'SIGNAL_RUN_LAST']: + new_name = name.split("_", 1)[-1] + globals()[name] = getattr(GObjectModule.SignalFlags, new_name) + deprecated_attr("GObject", name, "GObject.SignalFlags." + new_name) + __all__.append(name) # Static types GBoxed = _gobject.GBoxed @@ -705,4 +708,5 @@ SignalOverride = signalhelper.SignalOverride # Deprecated naming "property" available for backwards compatibility. # Keep this at the end of the file to avoid clobbering the builtin. property = Property +deprecated_attr("GObject", "property", "GObject.Property") __all__ += ['Property', 'Signal', 'SignalOverride', 'property'] diff --git a/gi/overrides/__init__.py b/gi/overrides/__init__.py index b337b355..62cfd308 100644 --- a/gi/overrides/__init__.py +++ b/gi/overrides/__init__.py @@ -14,6 +14,10 @@ from pkgutil import extend_path __path__ = extend_path(__path__, __name__) +# namespace -> (attr, replacement) +_deprecated_attrs = {} + + def wraps(wrapped): def assign(wrapper): wrapper.__name__ = wrapped.__name__ @@ -43,6 +47,37 @@ class OverridesProxyModule(types.ModuleType): return "<%s %r>" % (type(self).__name__, self._introspection_module) +class _DeprecatedAttribute(object): + """A deprecation descriptor for OverridesProxyModule subclasses. + + Emits a PyGIDeprecationWarning on every access and tries to act as a + normal instance attribute (can be replaced and deleted). + """ + + def __init__(self, namespace, attr, value, replacement): + self._attr = attr + self._value = value + self._warning = PyGIDeprecationWarning( + '%s.%s is deprecated; use %s instead' % ( + namespace, attr, replacement)) + + def __get__(self, instance, owner): + if instance is None: + raise AttributeError(self._attr) + warnings.warn(self._warning, stacklevel=2) + return self._value + + def __set__(self, instance, value): + attr = self._attr + # delete the descriptor, then set the instance value + delattr(type(instance), attr) + setattr(instance, attr, value) + + def __delete__(self, instance): + # delete the descriptor + delattr(type(instance), self._attr) + + def load_overrides(introspection_module): """Loads overrides for an introspection module. @@ -58,7 +93,11 @@ def load_overrides(introspection_module): has_old = module_key in sys.modules old_module = sys.modules.get(module_key) - proxy = OverridesProxyModule(introspection_module) + # Create a new sub type, so we can separate descriptors like + # _DeprecatedAttribute for each namespace. + proxy_type = type(namespace + "ProxyModule", (OverridesProxyModule, ), {}) + + proxy = proxy_type(introspection_module) sys.modules[module_key] = proxy # backwards compat: @@ -90,6 +129,19 @@ def load_overrides(introspection_module): continue setattr(proxy, var, item) + # Replace deprecated module level attributes with a descriptor + # which emits a warning when accessed. + for attr, replacement in _deprecated_attrs.pop(namespace, []): + try: + value = getattr(proxy, attr) + except AttributeError: + raise AssertionError( + "%s was set deprecated but wasn't added to __all__" % attr) + delattr(proxy, attr) + deprecated_attr = _DeprecatedAttribute( + namespace, attr, value, replacement) + setattr(proxy_type, attr, deprecated_attr) + return proxy @@ -152,6 +204,26 @@ def deprecated(fn, replacement): return wrapped +def deprecated_attr(namespace, attr, replacement): + """Marks a module level attribute as deprecated. Accessing it will emit + a PyGIDeprecationWarning warning. + + e.g. for ``deprecated_attr("GObject", "STATUS_FOO", "GLib.Status.FOO")`` + accessing GObject.STATUS_FOO will emit: + + "GObject.STATUS_FOO is deprecated; use GLib.Status.FOO instead" + + :param str namespace: + The namespace of the override this is called in. + :param str namespace: + The attribute name (which gets added to __all__). + :param str replacement: + The replacement text which will be included in the warning. + """ + + _deprecated_attrs.setdefault(namespace, []).append((attr, replacement)) + + def deprecated_init(super_init_func, arg_names, ignore=tuple(), deprecated_aliases={}, deprecated_defaults={}, category=PyGIDeprecationWarning, diff --git a/tests/test_gi.py b/tests/test_gi.py index dc40f3c5..a803529c 100644 --- a/tests/test_gi.py +++ b/tests/test_gi.py @@ -2789,6 +2789,76 @@ class TestDeprecation(unittest.TestCase): self.assertTrue(issubclass(warn[0].category, DeprecationWarning)) self.assertEqual(str(warn[0].message), "GLib.strcasecmp is deprecated") + def test_deprecated_attribute_compat(self): + # test if the deprecation descriptor behaves like an instance attribute + + # save the descriptor + desc = type(GLib).__dict__["IO_STATUS_ERROR"] + + # the descriptor raises AttributeError for itself + self.assertFalse(hasattr(type(GLib), "IO_STATUS_ERROR")) + + self.assertTrue(hasattr(GLib, "IO_STATUS_ERROR")) + + try: + # check if replacing works + GLib.IO_STATUS_ERROR = "foo" + self.assertEqual(GLib.IO_STATUS_ERROR, "foo") + finally: + # restore descriptor + try: + del GLib.IO_STATUS_ERROR + except AttributeError: + pass + setattr(type(GLib), "IO_STATUS_ERROR", desc) + + try: + # check if deleting works + del GLib.IO_STATUS_ERROR + self.assertFalse(hasattr(GLib, "IO_STATUS_ERROR")) + finally: + # restore descriptor + try: + del GLib.IO_STATUS_ERROR + except AttributeError: + pass + setattr(type(GLib), "IO_STATUS_ERROR", desc) + + def test_deprecated_attribute_warning(self): + with warnings.catch_warnings(record=True) as warn: + warnings.simplefilter('always') + self.assertEqual(GLib.IO_STATUS_ERROR, GLib.IOStatus.ERROR) + GLib.IO_STATUS_ERROR + GLib.IO_STATUS_ERROR + self.assertEqual(len(warn), 3) + self.assertTrue( + issubclass(warn[0].category, PyGIDeprecationWarning)) + self.assertRegexpMatches( + str(warn[0].message), + ".*GLib.IO_STATUS_ERROR.*GLib.IOStatus.ERROR.*") + + def test_deprecated_attribute_warning_coverage(self): + with warnings.catch_warnings(record=True) as warn: + warnings.simplefilter('always') + GObject.markup_escape_text + GObject.PRIORITY_DEFAULT + GObject.GError + GObject.PARAM_CONSTRUCT + GObject.SIGNAL_ACTION + GObject.property + GObject.IO_STATUS_ERROR + GObject.G_MAXUINT64 + GLib.IO_STATUS_ERROR + GLib.SPAWN_SEARCH_PATH + GLib.OPTION_FLAG_HIDDEN + GLib.IO_FLAG_IS_WRITEABLE + GLib.IO_FLAG_NONBLOCK + GLib.USER_DIRECTORY_DESKTOP + GLib.OPTION_ERROR_BAD_VALUE + GLib.glib_version + GLib.pyglib_version + self.assertEqual(len(warn), 17) + def test_deprecated_init_no_keywords(self): def init(self, **kwargs): self.assertDictEqual(kwargs, {'a': 1, 'b': 2, 'c': 3}) |