Source code for pyinthesky.methods
# XXX: Suggestions for improvement.
# 1. The ability to indicate what arguments should be passed in
# positionally. True for all, or sequence for specific ordering.
# 2. Support for *args
# 3. Support for **kwargs.
#
# Should be called func_sig_wrapper
from functools import partial
import six
[docs]def method_sig_wrapper(target, name, varnames, defaults=None,
make_method=False):
# First of all, defaults will need to be a tuple rather than a
# dictionary.
defaults_l = []
# Translating the dictionary into the sequence required.
if defaults:
defaults_d = defaults.copy()
# We get the X last variable names - they are presumably the
# variables which have defaults.
for argname in varnames[-len(defaults):]:
try:
defaults_l.append(defaults_d.pop(argname))
except KeyError:
# XXX: We've got awkward method definitions - we need to
# be able to handle this gracefully:
# SetAVTransportURI ['InstanceID', 'CurrentURIMetaData', 'CurrentURI'] {'CurrentURIMetaData': 'NOT_IMPLEMENTED'}
defaults_l = []
defaults_d = {}
break
# raise ValueError("require default value for '%s'" % argname)
if defaults_d:
raise ValueError("default provided for unspecified varname: '%s'" % defaults_d.keys()[0])
del defaults
if make_method:
argstr = ', '.join(['self'] + list(varnames))
wrapfunc = (
"def {name}({argstr}): __locals__ = locals(); "
"__locals__.pop('self'); return _target_(**__locals__)\n"
)
else:
argstr = ', '.join(varnames)
wrapfunc = 'def {name}({argstr}): return _target_(**locals())\n'
wrapfunc = wrapfunc.format(**vars())
wrapcode = compile(wrapfunc, '<generated_function>', 'exec')
global_ns = {}
eval(wrapcode, {'_target_': target}, global_ns)
f = global_ns[name]
f.func_defaults = tuple(defaults_l)
return f
_ambiguous_docstring = '''
This is a convenience function which cannot be used, as there are multiple
methods available with the same name. You will have to call the required
methods more directly:
'''.strip() + '\n'
[docs]def make_ambiguous_function(name, func_locations):
e = 'method "%s" is ambiguous - will need to invoke directly from service'
def cant_do_it(*args, **kwargs):
raise RuntimeError(e % name)
cant_do_it.__name__ = name
describes = '\n'.join([' - ' + fl for fl in func_locations])
cant_do_it.__doc__ = _ambiguous_docstring + describes
return cant_do_it
# XXX: Add docstrings...
[docs]def bind_service_methods(target, services=None, bind_to_class=False):
if services is None:
services = [target]
from pyinthesky import meta
bind_to_meta = bind_target_class = False
if bind_to_class is True:
target_class = target.__class__
elif bind_to_class is False:
target_class = None
elif hasattr(meta, bind_to_class):
target_class = getattr(meta, bind_to_class)
bind_target_class = True
else:
target_class = type(bind_to_class, (target.__class__,), {})
target_class.__module__ = 'pyinthesky.meta'
bind_to_meta = True
bind_target_class = True
serv_methods = {}
for service in services:
for methname, (in_args, out_args) in service.methods.items():
func = partial(service.invoke, methname)
args = list(in_args.argument_order)
f = method_sig_wrapper(func, methname, args,
in_args.argument_defaults,
make_method=target_class is not None)
serv_methods.setdefault(methname, []).append([
f, service.name + '.' + methname,
])
# We've now built functions for all remote methods, so see what we
# can set and what we can't.
for methname, methods in serv_methods.items():
if len(methods) == 1:
f = methods[0][0]
else:
f = make_ambiguous_function(methname, [x[1] for x in methods])
if target_class is None:
setattr(target, methname, f)
else:
m = six.create_unbound_method(f, target_class)
setattr(target_class, methname, m)
if bind_target_class:
target.__class__ = target_class
if bind_to_meta:
setattr(meta, target_class.__name__, target_class)
return dict((k, len(serv_methods[k]) == 1) for k in serv_methods)