Add sphinx based documentation
[pygobject.git] / gi / module.py
blob0b22634218f7a2341bcd9dd9be431052bb7707b9
1 # -*- Mode: Python; py-indent-offset: 4 -*-
2 # vim: tabstop=4 shiftwidth=4 expandtab
4 # Copyright (C) 2007-2009 Johan Dahlin <johan@gnome.org>
6 # module.py: dynamic module for introspected libraries.
8 # This library is free software; you can redistribute it and/or
9 # modify it under the terms of the GNU Lesser General Public
10 # License as published by the Free Software Foundation; either
11 # version 2.1 of the License, or (at your option) any later version.
13 # This library is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 # Lesser General Public License for more details.
18 # You should have received a copy of the GNU Lesser General Public
19 # License along with this library; if not, write to the Free Software
20 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
21 # USA
23 from __future__ import absolute_import
25 import sys
26 import importlib
28 _have_py3 = (sys.version_info[0] >= 3)
30 try:
31 maketrans = ''.maketrans
32 except AttributeError:
33 # fallback for Python 2
34 from string import maketrans
36 import gi
38 from ._gi import \
39 Repository, \
40 FunctionInfo, \
41 RegisteredTypeInfo, \
42 EnumInfo, \
43 ObjectInfo, \
44 InterfaceInfo, \
45 ConstantInfo, \
46 StructInfo, \
47 UnionInfo, \
48 CallbackInfo, \
49 Struct, \
50 Boxed, \
51 CCallback, \
52 enum_add, \
53 enum_register_new_gtype_and_add, \
54 flags_add, \
55 flags_register_new_gtype_and_add, \
56 GInterface
57 from .types import \
58 GObjectMeta, \
59 StructMeta
61 from ._constants import \
62 TYPE_NONE, \
63 TYPE_BOXED, \
64 TYPE_POINTER, \
65 TYPE_ENUM, \
66 TYPE_FLAGS
69 repository = Repository.get_default()
71 # Cache of IntrospectionModules that have been loaded.
72 _introspection_modules = {}
75 def get_parent_for_object(object_info):
76 parent_object_info = object_info.get_parent()
78 if not parent_object_info:
79 # If we reach the end of the introspection info class hierarchy, look
80 # for an existing wrapper on the GType and use it as a base for the
81 # new introspection wrapper. This allows static C wrappers already
82 # registered with the GType to be used as the introspection base
83 # (_gi.GObject for example)
84 gtype = object_info.get_g_type()
85 if gtype and gtype.pytype:
86 return gtype.pytype
88 # Otherwise use builtins.object as the base
89 return object
91 namespace = parent_object_info.get_namespace()
92 name = parent_object_info.get_name()
94 module = importlib.import_module('gi.repository.' + namespace)
95 return getattr(module, name)
98 def get_interfaces_for_object(object_info):
99 interfaces = []
100 for interface_info in object_info.get_interfaces():
101 namespace = interface_info.get_namespace()
102 name = interface_info.get_name()
104 module = importlib.import_module('gi.repository.' + namespace)
105 interfaces.append(getattr(module, name))
106 return interfaces
109 class IntrospectionModule(object):
110 """An object which wraps an introspection typelib.
112 This wrapping creates a python module like representation of the typelib
113 using gi repository as a foundation. Accessing attributes of the module
114 will dynamically pull them in and create wrappers for the members.
115 These members are then cached on this introspection module.
117 def __init__(self, namespace, version=None):
118 """Might raise gi._gi.RepositoryError"""
120 repository.require(namespace, version)
121 self._namespace = namespace
122 self._version = version
123 self.__name__ = 'gi.repository.' + namespace
125 self.__path__ = repository.get_typelib_path(self._namespace)
126 if _have_py3:
127 # get_typelib_path() delivers bytes, not a string
128 self.__path__ = self.__path__.decode('UTF-8')
130 if self._version is None:
131 self._version = repository.get_version(self._namespace)
133 def __getattr__(self, name):
134 info = repository.find_by_name(self._namespace, name)
135 if not info:
136 raise AttributeError("%r object has no attribute %r" % (
137 self.__name__, name))
139 if isinstance(info, EnumInfo):
140 g_type = info.get_g_type()
141 wrapper = g_type.pytype
143 if wrapper is None:
144 if info.is_flags():
145 if g_type.is_a(TYPE_FLAGS):
146 wrapper = flags_add(g_type)
147 else:
148 assert g_type == TYPE_NONE
149 wrapper = flags_register_new_gtype_and_add(info)
150 else:
151 if g_type.is_a(TYPE_ENUM):
152 wrapper = enum_add(g_type)
153 else:
154 assert g_type == TYPE_NONE
155 wrapper = enum_register_new_gtype_and_add(info)
157 wrapper.__info__ = info
158 wrapper.__module__ = 'gi.repository.' + info.get_namespace()
160 # Don't use upper() here to avoid locale specific
161 # identifier conversion (e. g. in Turkish 'i'.upper() == 'i')
162 # see https://bugzilla.gnome.org/show_bug.cgi?id=649165
163 ascii_upper_trans = maketrans(
164 'abcdefgjhijklmnopqrstuvwxyz',
165 'ABCDEFGJHIJKLMNOPQRSTUVWXYZ')
166 for value_info in info.get_values():
167 value_name = value_info.get_name_unescaped().translate(ascii_upper_trans)
168 setattr(wrapper, value_name, wrapper(value_info.get_value()))
169 for method_info in info.get_methods():
170 setattr(wrapper, method_info.__name__, method_info)
172 if g_type != TYPE_NONE:
173 g_type.pytype = wrapper
175 elif isinstance(info, RegisteredTypeInfo):
176 g_type = info.get_g_type()
178 # Create a wrapper.
179 if isinstance(info, ObjectInfo):
180 parent = get_parent_for_object(info)
181 interfaces = tuple(interface for interface in get_interfaces_for_object(info)
182 if not issubclass(parent, interface))
183 bases = (parent,) + interfaces
184 metaclass = GObjectMeta
185 elif isinstance(info, CallbackInfo):
186 bases = (CCallback,)
187 metaclass = GObjectMeta
188 elif isinstance(info, InterfaceInfo):
189 bases = (GInterface,)
190 metaclass = GObjectMeta
191 elif isinstance(info, (StructInfo, UnionInfo)):
192 if g_type.is_a(TYPE_BOXED):
193 bases = (Boxed,)
194 elif (g_type.is_a(TYPE_POINTER) or
195 g_type == TYPE_NONE or
196 g_type.fundamental == g_type):
197 bases = (Struct,)
198 else:
199 raise TypeError("unable to create a wrapper for %s.%s" % (info.get_namespace(), info.get_name()))
200 metaclass = StructMeta
201 else:
202 raise NotImplementedError(info)
204 # Check if there is already a Python wrapper that is not a parent class
205 # of the wrapper being created. If it is a parent, it is ok to clobber
206 # g_type.pytype with a new child class wrapper of the existing parent.
207 # Note that the return here never occurs under normal circumstances due
208 # to caching on the __dict__ itself.
209 if g_type != TYPE_NONE:
210 type_ = g_type.pytype
211 if type_ is not None and type_ not in bases:
212 self.__dict__[name] = type_
213 return type_
215 dict_ = {
216 '__info__': info,
217 '__module__': 'gi.repository.' + self._namespace,
218 '__gtype__': g_type
220 wrapper = metaclass(name, bases, dict_)
222 # Register the new Python wrapper.
223 if g_type != TYPE_NONE:
224 g_type.pytype = wrapper
226 elif isinstance(info, FunctionInfo):
227 wrapper = info
228 elif isinstance(info, ConstantInfo):
229 wrapper = info.get_value()
230 else:
231 raise NotImplementedError(info)
233 # Cache the newly created wrapper which will then be
234 # available directly on this introspection module instead of being
235 # lazily constructed through the __getattr__ we are currently in.
236 self.__dict__[name] = wrapper
237 return wrapper
239 def __repr__(self):
240 path = repository.get_typelib_path(self._namespace)
241 if _have_py3:
242 # get_typelib_path() delivers bytes, not a string
243 path = path.decode('UTF-8')
244 return "<IntrospectionModule %r from %r>" % (self._namespace, path)
246 def __dir__(self):
247 # Python's default dir() is just dir(self.__class__) + self.__dict__.keys()
248 result = set(dir(self.__class__))
249 result.update(self.__dict__.keys())
251 # update *set* because some repository attributes have already been
252 # wrapped by __getattr__() and included in self.__dict__; but skip
253 # Callback types, as these are not real objects which we can actually
254 # get
255 namespace_infos = repository.get_infos(self._namespace)
256 result.update(info.get_name() for info in namespace_infos if
257 not isinstance(info, CallbackInfo))
259 return list(result)
262 def get_introspection_module(namespace):
264 :Returns:
265 An object directly wrapping the gi module without overrides.
267 Might raise gi._gi.RepositoryError
269 if namespace in _introspection_modules:
270 return _introspection_modules[namespace]
272 version = gi.get_required_version(namespace)
273 module = IntrospectionModule(namespace, version)
274 _introspection_modules[namespace] = module
275 return module