[pygobject] Refactor overrides import/modules
- From: Simon Feltman <sfeltman src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [pygobject] Refactor overrides import/modules
- Date: Sat, 24 Jan 2015 17:24:40 +0000 (UTC)
commit 149c31beced944c72fba6ca6e096c81c1100ea2b
Author: Christoph Reiter <reiter christoph gmail com>
Date: Tue Dec 2 15:38:57 2014 +0100
Refactor overrides import/modules
Removes Registry and DynamicModule in favor of a simple module wrapper that
contains only overrides and falls back to the introspection module.
Moves all the overrides logic into gi.overrides; Speeds up module attribute access
https://bugzilla.gnome.org/show_bug.cgi?id=736678
gi/importer.py | 11 ++--
gi/module.py | 68 ---------------------
gi/overrides/__init__.py | 131 +++++++++++++++++++++++++++++-----------
tests/test_gi.py | 4 -
tests/test_import_machinery.py | 32 ++++++++--
5 files changed, 126 insertions(+), 120 deletions(-)
---
diff --git a/gi/importer.py b/gi/importer.py
index cea0f8b..52d1a27 100644
--- a/gi/importer.py
+++ b/gi/importer.py
@@ -24,10 +24,13 @@ from __future__ import absolute_import
import sys
from ._gi import Repository
-from .module import DynamicModule
+from .module import get_introspection_module
+from .overrides import load_overrides
repository = Repository.get_default()
+
+# only for backwards compatibility
modules = {}
@@ -57,13 +60,11 @@ class DynamicImporter(object):
return sys.modules[fullname]
path, namespace = fullname.rsplit('.', 1)
- dynamic_module = DynamicModule(namespace)
- modules[namespace] = dynamic_module
+ introspection_module = get_introspection_module(namespace)
+ dynamic_module = load_overrides(introspection_module)
dynamic_module.__file__ = '<%s>' % fullname
dynamic_module.__loader__ = self
-
sys.modules[fullname] = dynamic_module
- dynamic_module._load()
return dynamic_module
diff --git a/gi/module.py b/gi/module.py
index 8c25fd7..f27d516 100644
--- a/gi/module.py
+++ b/gi/module.py
@@ -23,7 +23,6 @@
from __future__ import absolute_import
import sys
-import types
import importlib
_have_py3 = (sys.version_info[0] >= 3)
@@ -35,7 +34,6 @@ except AttributeError:
from string import maketrans
import gi
-from .overrides import registry
from ._gi import \
Repository, \
@@ -273,69 +271,3 @@ def get_introspection_module(namespace):
module = IntrospectionModule(namespace, version)
_introspection_modules[namespace] = module
return module
-
-
-class DynamicModule(types.ModuleType):
- """A module composed of an IntrospectionModule and an overrides module.
-
- DynamicModule wraps up an IntrospectionModule and an overrides module
- into a single accessible module. This is what is returned from statements
- like "from gi.repository import Foo". Accessing attributes on a DynamicModule
- will first look overrides (or the gi.overrides.registry cache) and then
- in the introspection module if it was not found as an override.
- """
- def __init__(self, namespace):
- self._namespace = namespace
- self._introspection_module = None
- self._overrides_module = None
- self.__path__ = None
-
- def _load(self):
- self._introspection_module = get_introspection_module(self._namespace)
- try:
- self._overrides_module = importlib.import_module('gi.overrides.' + self._namespace)
- except ImportError:
- self._overrides_module = None
-
- self.__path__ = repository.get_typelib_path(self._namespace)
- if _have_py3:
- # get_typelib_path() delivers bytes, not a string
- self.__path__ = self.__path__.decode('UTF-8')
-
- def __getattr__(self, name):
- if self._overrides_module is not None:
- override_exports = getattr(self._overrides_module, '__all__', ())
- if name in override_exports:
- return getattr(self._overrides_module, name, None)
- else:
- # check the registry just in case the module hasn't loaded yet
- # TODO: Only gtypes are registered in the registry right now
- # but it would be nice to register all overrides and
- # get rid of the module imports. We might actually see a
- # speedup.
- key = '%s.%s' % (self._namespace, name)
- if key in registry:
- return registry[key]
-
- return getattr(self._introspection_module, name)
-
- def __dir__(self):
- # Python's default dir() is just dir(self.__class__) + self.__dict__.keys()
- result = set(dir(self.__class__))
- result.update(self.__dict__.keys())
-
- result.update(dir(self._introspection_module))
- override_exports = getattr(self._overrides_module, '__all__', ())
- result.update(override_exports)
- return list(result)
-
- def __repr__(self):
- path = repository.get_typelib_path(self._namespace)
- if _have_py3:
- # get_typelib_path() delivers bytes, not a string
- path = path.decode('UTF-8')
-
- return "<%s.%s %r from %r>" % (self.__class__.__module__,
- self.__class__.__name__,
- self._namespace,
- path)
diff --git a/gi/overrides/__init__.py b/gi/overrides/__init__.py
index 35e14ea..b337b35 100644
--- a/gi/overrides/__init__.py
+++ b/gi/overrides/__init__.py
@@ -1,5 +1,7 @@
import types
import warnings
+import importlib
+import sys
from gi import PyGIDeprecationWarning
from gi._gi import CallableInfo
@@ -11,8 +13,6 @@ from gi._constants import \
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)
-registry = None
-
def wraps(wrapped):
def assign(wrapper):
@@ -22,40 +22,75 @@ def wraps(wrapped):
return assign
-class _Registry(dict):
- def __setitem__(self, key, value):
- """We do checks here to make sure only submodules of the override
- module are added. Key and value should be the same object and come
- from the gi.override module.
+class OverridesProxyModule(types.ModuleType):
+ """Wraps a introspection module and contains all overrides"""
- We add the override to the dict as "override_module.name". For instance
- if we were overriding Gtk.Button you would retrive it as such:
- registry['Gtk.Button']
- """
- if not key == value:
- raise KeyError('You have tried to modify the registry. This should only be done by the override
decorator')
+ def __init__(self, introspection_module):
+ super(OverridesProxyModule, self).__init__(
+ introspection_module.__name__)
+ self._introspection_module = introspection_module
- try:
- info = getattr(value, '__info__')
- except AttributeError:
- raise TypeError('Can not override a type %s, which is not in a gobject introspection typelib' %
value.__name__)
+ def __getattr__(self, name):
+ return getattr(self._introspection_module, name)
- if not value.__module__.startswith('gi.overrides'):
- raise KeyError('You have tried to modify the registry outside of the overrides module. '
- 'This is not allowed (%s, %s)' % (value, value.__module__))
+ def __dir__(self):
+ result = set(dir(self.__class__))
+ result.update(self.__dict__.keys())
+ result.update(dir(self._introspection_module))
+ return sorted(result)
+
+ def __repr__(self):
+ return "<%s %r>" % (type(self).__name__, self._introspection_module)
- g_type = info.get_g_type()
- assert g_type != TYPE_NONE
- if g_type != TYPE_INVALID:
- g_type.pytype = value
- # strip gi.overrides from module name
- module = value.__module__[13:]
- key = "%s.%s" % (module, value.__name__)
- super(_Registry, self).__setitem__(key, value)
+def load_overrides(introspection_module):
+ """Loads overrides for an introspection module.
- def register(self, override_class):
- self[override_class] = override_class
+ Either returns the same module again in case there are no overrides or a
+ proxy module including overrides. Doesn't cache the result.
+ """
+
+ namespace = introspection_module.__name__.rsplit(".", 1)[-1]
+ module_key = 'gi.repository.' + namespace
+
+ # We use sys.modules so overrides can import from gi.repository
+ # but restore everything at the end so this doesn't have any side effects
+ has_old = module_key in sys.modules
+ old_module = sys.modules.get(module_key)
+
+ proxy = OverridesProxyModule(introspection_module)
+ sys.modules[module_key] = proxy
+
+ # backwards compat:
+ # gedit uses gi.importer.modules['Gedit']._introspection_module
+ from ..importer import modules
+ assert hasattr(proxy, "_introspection_module")
+ modules[namespace] = proxy
+
+ try:
+ try:
+ override_mod = importlib.import_module('gi.overrides.' + namespace)
+ except ImportError:
+ return introspection_module
+ finally:
+ del modules[namespace]
+ del sys.modules[module_key]
+ if has_old:
+ sys.modules[module_key] = old_module
+
+ override_all = []
+ if hasattr(override_mod, "__all__"):
+ override_all = override_mod.__all__
+
+ for var in override_all:
+ try:
+ item = getattr(override_mod, var)
+ except (AttributeError, TypeError):
+ # Gedit puts a non-string in __all__, so catch TypeError here
+ continue
+ setattr(proxy, var, item)
+
+ return proxy
class overridefunc(object):
@@ -63,23 +98,47 @@ class overridefunc(object):
def __init__(self, func):
if not isinstance(func, CallableInfo):
raise TypeError("func must be a gi function, got %s" % func)
- from ..importer import modules
+
module_name = func.__module__.rsplit('.', 1)[-1]
- self.module = modules[module_name]._introspection_module
+ self.module = sys.modules["gi.repository." + module_name]
def __call__(self, func):
setattr(self.module, func.__name__, func)
return func
-registry = _Registry()
-
def override(type_):
- """Decorator for registering an override"""
+ """Decorator for registering an override.
+
+ Other than objects added to __all__, these can get referenced in the same
+ override module via the gi.repository module (get_parent_for_object() does
+ for example), so they have to be added to the module immediately.
+ """
+
if isinstance(type_, (types.FunctionType, CallableInfo)):
return overridefunc(type_)
else:
- registry.register(type_)
+ try:
+ info = getattr(type_, '__info__')
+ except AttributeError:
+ raise TypeError(
+ 'Can not override a type %s, which is not in a gobject '
+ 'introspection typelib' % type_.__name__)
+
+ if not type_.__module__.startswith('gi.overrides'):
+ raise KeyError(
+ 'You have tried override outside of the overrides module. '
+ 'This is not allowed (%s, %s)' % (type_, type_.__module__))
+
+ g_type = info.get_g_type()
+ assert g_type != TYPE_NONE
+ if g_type != TYPE_INVALID:
+ g_type.pytype = type_
+
+ namespace = type_.__module__.rsplit(".", 1)[-1]
+ module = sys.modules["gi.repository." + namespace]
+ setattr(module, type_.__name__, type_)
+
return type_
diff --git a/tests/test_gi.py b/tests/test_gi.py
index 20c7343..31b31f5 100644
--- a/tests/test_gi.py
+++ b/tests/test_gi.py
@@ -2203,10 +2203,6 @@ class TestPythonGObject(unittest.TestCase):
object_ = self.SubObject(int=1)
self.assertEqual(object_.vfunc_return_value_only(), 2121)
- def test_dynamic_module(self):
- from gi.module import DynamicModule
- self.assertTrue(isinstance(GObject, DynamicModule))
-
def test_subobject_non_vfunc_do_method(self):
class PythonObjectWithNonVFuncDoMethod(object):
def do_not_a_vfunc(self):
diff --git a/tests/test_import_machinery.py b/tests/test_import_machinery.py
index f68f522..0672aa7 100644
--- a/tests/test_import_machinery.py
+++ b/tests/test_import_machinery.py
@@ -14,7 +14,8 @@ except ImportError:
Regress = None
-class TestRegistry(unittest.TestCase):
+class TestOverrides(unittest.TestCase):
+
def test_non_gi(self):
class MyClass:
pass
@@ -31,6 +32,29 @@ class TestRegistry(unittest.TestCase):
# https://bugzilla.gnome.org/show_bug.cgi?id=680913
self.assertEqual(Regress.REGRESS_OVERRIDE, 42)
+ def test_load_overrides(self):
+ mod = gi.module.get_introspection_module('GIMarshallingTests')
+ mod_override = gi.overrides.load_overrides(mod)
+ self.assertTrue(mod_override is not mod)
+ self.assertTrue(mod_override._introspection_module is mod)
+ self.assertEqual(mod_override.OVERRIDES_CONSTANT, 7)
+ self.assertEqual(mod.OVERRIDES_CONSTANT, 42)
+
+ def test_load_no_overrides(self):
+ mod_key = "gi.overrides.GIMarshallingTests"
+ had_mod = mod_key in sys.modules
+ old_mod = sys.modules.get(mod_key)
+ try:
+ # this makes override import fail
+ sys.modules[mod_key] = None
+ mod = gi.module.get_introspection_module('GIMarshallingTests')
+ mod_override = gi.overrides.load_overrides(mod)
+ self.assertTrue(mod_override is mod)
+ finally:
+ del sys.modules[mod_key]
+ if had_mod:
+ sys.modules[mod_key] = old_mod
+
class TestModule(unittest.TestCase):
# Tests for gi.module
@@ -49,12 +73,6 @@ class TestModule(unittest.TestCase):
mod2 = gi.module.get_introspection_module(mod_name)
self.assertTrue(mod1 is mod2)
- # Using a DynamicModule will use get_introspection_module internally
- # in its _load method.
- mod_overridden = gi.module.DynamicModule(mod_name)
- mod_overridden._load()
- self.assertTrue(mod1 is mod_overridden._introspection_module)
-
# Restore the previous cache
gi.module._introspection_modules = old_modules
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]