def _method_factory(self, pid): """Create a custom function signature with docstring, instantiate it and pass it to a wrapper which will actually call the process. Parameters ---------- pid: str Identifier of the WPS process. Returns ------- func A Python function calling the remote process, complete with docstring and signature. """ process = self._processes[pid] required_inputs_first = sorted(process.dataInputs, key=sort_inputs_key) input_names = [] # defaults will be set to the function's __defaults__: # A tuple containing default argument values for those arguments that have defaults, # or None if no arguments have a default value. defaults = [] for inpt in required_inputs_first: input_names.append(sanitize(inpt.identifier)) if inpt.minOccurs == 0 or inpt.defaultValue is not None: default = inpt.defaultValue if inpt.dataType != "ComplexData" else None defaults.append(utils.from_owslib(default, inpt.dataType)) defaults = tuple(defaults) if defaults else None body = dedent(""" inputs = locals() inputs.pop('self') return self._execute('{pid}', **inputs) """).format(pid=pid) func_builder = FunctionBuilder( name=sanitize(pid), doc=utils.build_process_doc(process), args=["self"] + input_names, defaults=defaults, body=body, filename=__file__, module=self.__module__, ) self._inputs[pid] = {} if hasattr(process, "dataInputs"): self._inputs[pid] = OrderedDict( (i.identifier, i) for i in process.dataInputs) self._outputs[pid] = {} if hasattr(process, "processOutputs"): self._outputs[pid] = OrderedDict( (o.identifier, o) for o in process.processOutputs) func = func_builder.get_func() return func
def test_FunctionBuilder_modify(): fb = FunctionBuilder('return_five', doc='returns the integer 5', body='return 5') f = fb.get_func() assert f() == 5 fb.varkw = 'kw' f_kw = fb.get_func() assert f_kw(ignored_arg='ignored_val') == 5
def _method_factory(self, pid): """Create a custom function signature with docstring, instantiate it and pass it to a wrapper which will actually call the process. Parameters ---------- pid: str Identifier of the WPS process. Returns ------- func A Python function calling the remote process, complete with docstring and signature. """ process = self._processes[pid] input_defaults = OrderedDict() for inpt in process.dataInputs: iid = sanitize(inpt.identifier) default = getattr(inpt, "defaultValue", None) if inpt.dataType != 'ComplexData' else None input_defaults[iid] = utils.from_owslib(default, inpt.dataType) body = dedent(""" inputs = locals() inputs.pop('self') return self._execute('{pid}', **inputs) """).format(pid=pid) func_builder = FunctionBuilder( name=sanitize(pid), doc=utils.build_process_doc(process), args=["self"] + list(input_defaults), defaults=tuple(input_defaults.values()), body=body, filename=__file__, module=self.__module__, ) self._inputs[pid] = {} if hasattr(process, "dataInputs"): self._inputs[pid] = OrderedDict( (i.identifier, i) for i in process.dataInputs ) self._outputs[pid] = {} if hasattr(process, "processOutputs"): self._outputs[pid] = OrderedDict( (o.identifier, o) for o in process.processOutputs ) func = func_builder.get_func() return func
def test_get_invocation_sig_str( args, varargs, varkw, defaults, invocation_str, sig_str ): fb = FunctionBuilder( name='return_five', body='return 5', args=args, varargs=varargs, varkw=varkw, defaults=defaults ) assert fb.get_invocation_str() == invocation_str assert fb.get_sig_str() == sig_str
def test_remove_kwonly_arg(): # example adapted from https://github.com/mahmoud/boltons/issues/123 def darkhelm_inject_loop(func): sig = inspect.signature(func) loop_param = sig.parameters['loop'].replace(default=None) sig = sig.replace(parameters=[loop_param]) def add_loop(args, kwargs): bargs = sig.bind(*args, **kwargs) bargs.apply_defaults() if bargs.arguments['loop'] is None: bargs.arguments['loop'] = "don't look at me, I just use gevent" return bargs.arguments def wrapper(*args, **kwargs): return func(**add_loop(args, kwargs)) return wraps(func, injected=['loop'])(wrapper) @darkhelm_inject_loop def example(test='default', *, loop='lol'): return loop fb_example = FunctionBuilder.from_func(example) assert 'test' in fb_example.args assert fb_example.get_defaults_dict()['test'] == 'default' assert 'loop' not in fb_example.kwonlyargs assert 'loop' not in fb_example.kwonlydefaults
def wraps(func): """Custom wraps() function for decorators This one differs from functiools.wraps and boltons.funcutils.wraps in that it allows *adding* keyword arguments to the function signature. >>> def decorator(func): >>> @wraps(func) >>> def wrapper(*args, extra_param=None, **kwargs): >>> print("Called with extra_param=%s" % extra_param) >>> func(*args, **kwargs) >>> return wrapper >>> >>> @decorator() >>> def test(arg1, arg2, arg3='default'): >>> pass >>> >>> test('val1', 'val2', extra_param='xyz') """ fb = FunctionBuilder.from_func(func) def wrapper_wrapper(wrapper_func): fb_wrapper = FunctionBuilder.from_func(wrapper_func) fb.kwonlyargs += fb_wrapper.kwonlyargs fb.kwonlydefaults.update(fb_wrapper.kwonlydefaults) fb.body = 'return _call(%s)' % fb.get_invocation_str() execdict = dict(_call=wrapper_func, _func=func) fully_wrapped = fb.get_func(execdict) fully_wrapped.__wrapped__ = func return fully_wrapped return wrapper_wrapper
def __repr__(self): chunks = [self.__class__.__name__] if self.subspec != T: chunks.append('({!r})'.format(self.subspec)) else: chunks.append('()') for fname, args, _ in reversed(self._iter_stack): meth = getattr(self, fname) fb = FunctionBuilder.from_func(meth) fb.args = fb.args[1:] # drop self arg_names = fb.get_arg_names() # TODO: something fancier with defaults: chunks.append("." + fname) if len(args) == 0: chunks.append("()") elif len(arg_names) == 1: assert len(args) == 1 chunks.append('({!r})'.format(args[0])) elif arg_names: chunks.append('({})'.format(", ".join([ '{}={!r}'.format(name, val) for name, val in zip(arg_names, args) ]))) else: # p much just slice bc no kwargs chunks.append('({})'.format(", ".join(['%s' % a for a in args]))) return ''.join(chunks)
def test_get_arg_names(): def example(req, test='default'): return req fb_example = FunctionBuilder.from_func(example) assert 'test' in fb_example.args assert fb_example.get_arg_names() == ('req', 'test') assert fb_example.get_arg_names(only_required=True) == ('req',)
def test_defaults_dict(): def example(req, test='default'): return req fb_example = FunctionBuilder.from_func(example) assert 'test' in fb_example.args dd = fb_example.get_defaults_dict() assert dd['test'] == 'default' assert 'req' not in dd
def wrapper_wrapper(wrapper_func): fb_wrapper = FunctionBuilder.from_func(wrapper_func) fb.kwonlyargs += fb_wrapper.kwonlyargs fb.kwonlydefaults.update(fb_wrapper.kwonlydefaults) fb.body = 'return _call(%s)' % fb.get_invocation_str() execdict = dict(_call=wrapper_func, _func=func) fully_wrapped = fb.get_func(execdict) fully_wrapped.__wrapped__ = func return fully_wrapped
def test_get_invocation_sig_str( args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, invocation_str, sig_str, ): fb = FunctionBuilder( name="return_five", body="return 5", args=args, varargs=varargs, varkw=varkw, defaults=defaults, kwonlyargs=kwonlyargs, kwonlydefaults=kwonlydefaults, ) assert fb.get_invocation_str() == invocation_str assert fb.get_sig_str() == sig_str
def get_fb(f, drop_self=True): # TODO: support partials if not (inspect.isfunction(f) or inspect.ismethod(f) or \ inspect.isbuiltin(f)) and hasattr(f, '__call__'): if isinstance(getattr(f, '_sinter_fb', None), FunctionBuilder): return f._sinter_fb f = f.__call__ # callable objects if isinstance(getattr(f, '_sinter_fb', None), FunctionBuilder): return f._sinter_fb # we'll take your word for it; good luck, lil buddy. ret = FunctionBuilder.from_func(f) if not all([isinstance(a, str) for a in ret.args]): raise TypeError('does not support anonymous tuple arguments' ' or any other strange args for that matter.') if drop_self and isinstance(f, types.MethodType): ret.args = ret.args[1:] # discard "self" on methods return ret
def proxy(remote_method: RemoteMethod, ordinal: int) -> Callable: """ Generate a method proxying a remote method. The method will have the same signature as the remote method. However, its body will consist of a call to the ``Proxy`` class ``_call()`` method to forward the method call across the network. :param remote_method: Remote method to proxy. :param ordinal: ordinal of remote method in class. """ signature = inspect.signature(remote_method) # check that all parameters are positional or keyword and are serializable if not issubclass(signature.return_annotation, Serializable): raise TypeError( f"return type {signature.return_annotation} is not serializable") returntype = signature.return_annotation # skip "self" argnames: list[str] = list(signature.parameters)[1:] async def body(self: Proxy, *args: Serializable) -> returntype: arguments = bytearray() for i, arg in enumerate(args): try: arguments.extend(arg.serialize()) except Exception as e: raise ArgumentSerializationError(f"argument {i}: {arg}") from e ret = await self._client.call(ordinal, arguments) try: return returntype.deserialize(ret) except Exception as e: raise ReturnDeserializationError(f"of type {returntype}") from e builder = FunctionBuilder.from_func(remote_method) builder.is_async = True builder.body = f"return await body(self, {','.join(argnames)})" return builder.get_func(execdict={"body": body})
def test_FunctionBuilder_add_arg_kwonly(): fb = FunctionBuilder('return_val', doc='returns the value', body='return val') broken_func = fb.get_func() with pytest.raises(NameError): broken_func() fb.add_arg('val', default='default_val', kwonly=True) better_func = fb.get_func() assert better_func() == 'default_val' with pytest.raises(ValueError): fb.add_arg('val') assert better_func(val='keyword') == 'keyword' with pytest.raises(TypeError): assert better_func('positional') return
def __repr__(self): base_args = () if self.subspec != T: base_args = (self.subspec, ) base = format_invocation(self.__class__.__name__, base_args, repr=bbrepr) chunks = [base] for fname, args, _ in reversed(self._iter_stack): meth = getattr(self, fname) fb = FunctionBuilder.from_func(meth) fb.args = fb.args[1:] # drop self arg_names = fb.get_arg_names() # TODO: something fancier with defaults: kwargs = [] if len(args) > 1 and arg_names: args, kwargs = (), zip(arg_names, args) chunks.append('.' + format_invocation(fname, args, kwargs, repr=bbrepr)) return ''.join(chunks)
def test_FunctionBuilder_add_arg(): fb = FunctionBuilder('return_five', doc='returns the integer 5', body='return 5') f = fb.get_func() assert f() == 5 fb.add_arg('val') f = fb.get_func() assert f(val='ignored') == 5 with pytest.raises(ValueError) as excinfo: fb.add_arg('val') excinfo.typename == 'ExistingArgument' fb = FunctionBuilder('return_val', doc='returns the value', body='return val') broken_func = fb.get_func() with pytest.raises(NameError): broken_func() fb.add_arg('val', default='default_val') better_func = fb.get_func() assert better_func() == 'default_val' assert better_func('positional') == 'positional' assert better_func(val='keyword') == 'keyword'
try: try: glom(target, spec) except GlomError as e: stack = _norm_stack(traceback.format_exc(), e) finally: traceback._some_str = _orig_some_str return stack # quick way to get a function in this file, which doesn't have a glom # package file path prefix on it. this prevents the function getting # removed in the stack flattening. from boltons.funcutils import FunctionBuilder fb = FunctionBuilder(name='_raise_exc', body='raise Exception("unique message")', args=['t']) _raise_exc = fb.get_func() # NB: if we keep this approach, eventually # boltons.funcutils.FunctionBuilder will put lines into the linecache, # and comparisons may break def test_regular_error_stack(): actual = _make_stack({'results': [{'value': _raise_exc}]}) expected = """\ Traceback (most recent call last): File "test_error.py", line ___, in _make_stack glom(target, spec) File "core.py", line ___, in glom
def test_FunctionBuilder_invalid_body(): with pytest.raises(SyntaxError): FunctionBuilder(name="fails", body="*").get_func()
def test_FunctionBuilder_invalid_args(): with pytest.raises(TypeError): FunctionBuilder(name="fails", foo="bar")