[pygobject] Refactor overrides import/modules



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]