26
26
authors to write their own classes to be used instead of the generic one that
29
This module contains two global dictionaries, both are keys by unique
30
connection id's (UUID objects). The values are described below.
32
* ``_object_registry`` contains dictionaries of class names (as strings)
33
to class objects. Custom proxy classes defined by test authors will end up
34
with their class object in this dictionary. This is used when we want to
35
create a proxy instance - if nothing matches in this dictionary then we
36
create a generic proxy instance instead.
38
* ``_proxy_extensions`` contains a tuple of extension classes to mix in to
39
*every proxy class*. This is used to extend the proxy API on a
40
per-connection basis. For example, Qt-based apps allow us to monitor
41
signals and slots in the application, but Gtk apps do not.
31
45
from uuid import uuid4
35
49
from contextlib import contextmanager
37
51
_object_registry = {}
52
_proxy_extensions = {}
55
def register_extension_classes_for_proxy_base(proxy_base, extensions):
56
global _proxy_extensions
57
_proxy_extensions[proxy_base._id] = (proxy_base,) + extensions
60
def _get_proxy_bases_for_id(id):
61
global _proxy_extensions
62
return _proxy_extensions.get(id, ())
40
65
class IntrospectableObjectMetaclass(type):
50
75
custom proxy classes for more than one process at the same time and
51
76
avoid clashes in the dictionary.
54
# ignore the classes at the top of the inheritance heirarchy (i.e.- the
55
# ones that we control.)
57
'ApplicationProxyObject',
59
'DBusIntrospectionObject',
60
'DBusIntrospectionObjectBase',
62
# also ignore classes that derive from a class that already has the
64
have_id = any([hasattr(b, '_id') for b in bases])
81
if hasattr(base, '_id'):
85
# Ignore classes that are in the autopilot class heirarchy:
87
'ApplicationProxyObject',
89
'DBusIntrospectionObject',
90
'DBusIntrospectionObjectBase',
66
92
# Add the '_id' attribute as a class attr:
67
classdict['_id'] = uuid4()
93
cls_id = classdict['_id'] = uuid4()
95
# use the bases passed to us, but extend it with whatever is stored in
96
# the proxy_extensions dictionary.
97
extensions = _get_proxy_bases_for_id(cls_id)
98
for extension in extensions:
99
if extension not in bases:
100
bases += (extension,)
69
101
# make the object. Nothing special here.
70
102
class_object = type.__new__(cls, classname, bases, classdict)
72
# If the newly made object has an id, add it to the object registry.
73
if getattr(class_object, '_id', None) is not None:
74
if class_object._id in _object_registry:
75
_object_registry[class_object._id][classname] = class_object
77
_object_registry[class_object._id] = {classname: class_object}
104
if not classdict.get('__generated', False):
105
# If the newly made object has an id, add it to the object
107
if getattr(class_object, '_id', None) is not None:
108
if class_object._id in _object_registry:
109
_object_registry[class_object._id][classname] = \
112
_object_registry[class_object._id] = \
113
{classname: class_object}
78
114
# in all cases, return the class unchanged.
79
115
return class_object
89
def _get_proxy_object_class(object_id, default_class, path, state):
125
def _get_proxy_object_class(object_id, path, state):
90
126
"""Return a custom proxy class, from the object registry or the default.
92
128
This function first inspects the object registry using the object_id passed
105
141
:raises ValueError: if more than one class in the dict matches
109
if object_id is not None:
110
class_type = _try_custom_proxy_classes(object_id, path, state)
144
class_type = _try_custom_proxy_classes(object_id, path, state)
112
146
return class_type or _get_default_proxy_class(
114
148
get_classname_from_path(path)
132
166
if len(possible_classes) > 1:
133
167
raise ValueError(
134
168
'More than one custom proxy class matches this object: '
135
'Matching classes are: %s. State is %s. Path is %s.'
136
','.join([repr(c) for c in possible_classes]),
169
'Matching classes are: %s. State is %s. Path is %s.' % (
170
','.join([repr(c) for c in possible_classes]),
139
175
if len(possible_classes) == 1:
140
176
return possible_classes[0]
144
def _get_default_proxy_class(default_class, name):
180
def _get_default_proxy_class(id, name):
145
181
"""Return a custom proxy object class of the default or a base class.
147
183
We want the object to inherit from the class that is set as the emulator
156
192
get_debug_logger().warning(
157
193
"Generating introspection instance for type '%s' based on generic "
159
for base in default_class.__bases__:
160
if hasattr(base, '_id'):
164
base_class = default_class
165
195
if isinstance(name, bytes):
166
196
name = name.decode('utf-8')
167
return type(name, (base_class,), {})
197
return type(name, _get_proxy_bases_for_id(id), dict(__generated=True))