def sign_defaults(symbol, expr, composition): '''gets defaults from an expression using composition''' defaults = {} for f_ in get_undefined_funcs(expr): # includes {h(f(g)), f(g)} if str(f_) in composition: # ignores h(f(g)) f_defaults = get_defaults(composition[str(f_)]) # flatten defaults for arg, arg_default in f_defaults.items(): defaults[arg] = arg_default arg_signatures = [] # defaults have to go last, which may conflict with user's ordering for arg in symbol.args: str_arg = str(arg) if str_arg in defaults: arg_default = defaults[str(arg)] arg_signatures.append(forge.arg(str_arg, default=arg_default)) else: arg_signatures.append(forge.arg(str_arg)) # will raise an error if defaults are not last signature = forge.sign(*arg_signatures) return signature
def test_revise_args_order_preserved(self): """ Ensure that the var-positional arguments *aren't* re-ordered """ param_a = forge.arg('a') param_b = forge.arg('b') rev = synthesize(param_b, param_a) assert rev.revise(FSignature()) == FSignature([param_b, param_a])
def test_revise_kwargs_reordered(self): """ Ensure that the var-keyword arguments *are* re-ordered """ param_a = forge.arg('a') param_b = forge.arg('b') rev = synthesize(b=param_b, a=param_a) assert rev.revise(FSignature()) == FSignature([param_a, param_b])
def test_revise_args_precede_kwargs(self): """ Ensure that var-postional arguments precede var-keyword arguments """ param_a = forge.arg('a') param_b = forge.arg('b') rev = synthesize(param_b, a=param_a) assert rev.revise(FSignature()) == FSignature([param_b, param_a])
def test_revise_no_validation(self): """ Ensure no validation is performed on the revision """ rev = synthesize(forge.arg('b'), forge.pos('a')) assert rev.revise(FSignature()) == FSignature( [forge.arg('b'), forge.pos('a')], __validate_parameters__=False, )
def page_number(func): """ Decorator for adding pagination behavior to a view. That decorator produces a view based on page numbering and it adds three query parameters to control the pagination: page, page_size and count. Page has a default value of first page, page_size default value is defined in :class:`PageNumberResponse` and count defines if the response will define the total number of elements. The output schema is also modified by :class:`PageNumberSchema`, creating a new schema based on it but using the old output schema as the content of its data field. :param func: View to be decorated. :return: Decorated view. """ assert forge is not None, "`python-forge` must be installed to use Paginator." resource_schema = get_output_schema(func) data_schema = marshmallow.fields.Nested(resource_schema, many=True) if resource_schema else marshmallow.fields.Raw() schema = type( "PageNumberPaginated" + resource_schema.__class__.__name__, # Add a prefix to avoid collision (PageNumberSchema,), {"data": data_schema}, # Replace generic with resource schema )() forge_revision_list = ( forge.copy(func), forge.insert(forge.arg("page", default=None, type=int), index=-1), forge.insert(forge.arg("page_size", default=None, type=int), index=-1), forge.insert(forge.arg("count", default=True, type=bool), index=-1), forge.delete("kwargs"), forge.returns(schema), ) try: if asyncio.iscoroutinefunction(func): @forge.compose(*forge_revision_list) @functools.wraps(func) async def decorator(*args, page: int = None, page_size: int = None, count: bool = True, **kwargs): return PageNumberResponse( schema=schema, page=page, page_size=page_size, count=count, content=await func(*args, **kwargs) ) else: @forge.compose(*forge_revision_list) @functools.wraps(func) def decorator(*args, page: int = None, page_size: int = None, count: bool = True, **kwargs): return PageNumberResponse( schema=schema, page=page, page_size=page_size, count=count, content=func(*args, **kwargs) ) except ValueError as e: raise TypeError("Paginated views must define **kwargs param") from e return decorator
def test_revise_no_validation(self): """ Ensure no validation is performed on the revision """ rev = insert(forge.arg('b'), index=0) fsig = FSignature([forge.pos('a')], __validate_parameters__=False) assert rev.revise(fsig) == FSignature( [forge.arg('b'), forge.pos('a')], __validate_parameters__=False, )
def test_revise_no_validation(self): """ Ensure no validation is performed on the revision """ rev = modify('b', kind=POSITIONAL_ONLY) in_ = FSignature([forge.arg('a'), forge.arg('b')]) out_ = FSignature( [forge.arg('a'), forge.pos('b')], __validate_parameters__=False, ) assert rev.revise(in_) == out_
def test_revise_no_validation(self): """ Ensure no validation is performed on the revision """ rev = translocate('b', index=0) in_ = FSignature([forge.pos('a'), forge.arg('b')]) out_ = FSignature( [forge.arg('b'), forge.pos('a')], __validate_parameters__=False, ) assert rev.revise(in_) == out_
def _build_signature(cls, obj, with_fields=True, with_id=True, with_self=True, all_optional=False): parameters = [] if with_self: parameters.append(_forge.arg("self")) if with_id: parameters.append(_forge.arg(obj._FIELD_ID, type=int)) if with_fields: # Recompute FIELDS object fields = obj._FIELDS if isinstance(fields, list): fields = {key: str for key in fields} if isinstance(fields, dict): # The default format is (type, default_value). # This code tries to expand 'type' -> (type, "") # Since some of the type hints can be abstract, like typing.List[int], it has # to use the GenericAlias/GenericMeta code # see code in: # https://github.com/codepost-io/codepost-python/commit/2b0ae00d160ddae0b6540b32bfb2778f428dccd7#diff-b7cc0946b625e77d99cb9a61818cd773R44-R71 # There are two possibilities: # 1) (typ, default_value): a default value is provided -> use it # 2) typ: no default value is provided -> use "" instead fields = { key: (val, "") if is_type_variable(val) else val for (key, val) in fields.items() } # Create forge parameters for (key, val) in fields.items(): if key in obj._FIELDS_READ_ONLY: continue if all_optional or not key in obj._FIELDS_REQUIRED: parameters.append( _forge.arg(key, type=val[0], default=_FORGE_VOID)) else: parameters.append(_forge.arg(key, type=val[0])) return _forge.FSignature(parameters=parameters)
def test_revise(self, selector): """ Ensure that ``replace`` accepts selector values; i.e. those passed to ``findparam``. """ new_param = forge.arg('new') old_param = forge.arg('old') in_ = FSignature([old_param]) out_ = FSignature([new_param]) rev = replace(selector, new_param) assert rev.revise(in_) == out_
def test_revise(self): """ Ensure that manage revision passes the input signature to the user supplied function and returns (the user-defined function's) value. """ fsig = fsignature(lambda a, b, c: None) reverse = Mock( side_effect=lambda prev: prev.replace(parameters=prev[::-1])) rev = manage(reverse) assert rev.revise(fsig) == \ FSignature([forge.arg('c'), forge.arg('b'), forge.arg('a')])
def construct_signature(*args, **kwargs): """construct a signature usage: @forge.sign(*get_signature('x','y',z=3)) def f(*args, **kargs): pass """ signature = [] for arg in args: signature.append(forge.arg(arg)) for k, v in kwargs.items(): signature.append(forge.arg(k, default=v)) return signature
def test_revise_multiple(self, multiple): """ Ensure that passing ``multiple=True`` allows for modification of every parameter that matches the selector; i.e. values passed to ``findparam`` """ in_ = FSignature([forge.arg('a'), forge.arg('b')]) rev = modify(('a', 'b'), multiple=multiple, kind=POSITIONAL_ONLY) out_ = rev.revise(in_) kinds = [param.kind for param in out_] if multiple: assert kinds == [POSITIONAL_ONLY, POSITIONAL_ONLY] else: assert kinds == [POSITIONAL_ONLY, POSITIONAL_OR_KEYWORD]
def _eval_arguments(self, node): NONEXISTANT_DEFAULT = object() # a unique object to contrast with None posonlyargs_and_defaults = [] num_args = len(node.args) if hasattr(node, "posonlyargs"): for (arg, default) in itertools.zip_longest( node.posonlyargs[::-1], node.defaults[::-1][num_args:], fillvalue=NONEXISTANT_DEFAULT, ): if default is NONEXISTANT_DEFAULT: posonlyargs_and_defaults.append(forge.pos(arg.arg)) else: posonlyargs_and_defaults.append( forge.pos(arg.arg, default=self._eval(default))) posonlyargs_and_defaults.reverse() args_and_defaults = [] for (arg, default) in itertools.zip_longest( node.args[::-1], node.defaults[::-1][:num_args], fillvalue=NONEXISTANT_DEFAULT, ): if default is NONEXISTANT_DEFAULT: args_and_defaults.append(forge.arg(arg.arg)) else: args_and_defaults.append( forge.arg(arg.arg, default=self._eval(default))) args_and_defaults.reverse() vpo = (node.vararg and forge.args(node.vararg.arg)) or [] kwonlyargs_and_defaults = [] # kwonlyargs is 1:1 to kw_defaults, no need to jump through hoops for (arg, default) in zip(node.kwonlyargs, node.kw_defaults): if not default: kwonlyargs_and_defaults.append(forge.kwo(arg.arg)) else: kwonlyargs_and_defaults.append( forge.kwo(arg.arg, default=self._eval(default))) vkw = (node.kwarg and forge.kwargs(node.kwarg.arg)) or {} return ( [ *posonlyargs_and_defaults, *args_and_defaults, *vpo, *kwonlyargs_and_defaults, ], vkw, )
def decorator_gridify(f): signature = [] for arg, arg_default in defaults.items(): signature.append(forge.arg(arg, default=arg_default)) if order == 'A': indexing = 'xy' else: indexing = 'ij' @forge.sign(*signature) def wrapped(**kwargs): coordinates = np.meshgrid(*kwargs.values(), indexing=indexing, sparse=False, copy=False) points = np.column_stack([c.ravel() for c in coordinates]) if squeeze: out_shape = [-1] + list(coordinates[0].shape) return np.squeeze(f(points).reshape(out_shape, order=order)) else: out_shape = list(coordinates[0].shape) return f(points).reshape(out_shape, order=order) wrapped.__name__ = f.__name__ wrapped.__doc__ = f.__doc__ return decorate(wrapped, decorator_wrapper)
def test_no_position_raises(self): """ Ensure that ``index``, ``before``, or ``after`` must be passed """ with pytest.raises(TypeError) as excinfo: translocate(forge.arg('x')) assert excinfo.value.args[0] == \ "expected keyword argument 'index', 'before', or 'after'"
def test_no_position_raises(self): """ Ensure that insertion without index, before, or after raises. """ with pytest.raises(TypeError) as excinfo: insert(forge.arg('x')) assert excinfo.value.args[0] == \ "expected keyword argument 'index', 'before', or 'after'"
def test__call__bound_injected(self): """ Ensure ``bound`` fparams are injected into the mapping. """ fsig = FSignature([forge.arg('bound', default=1, bound=True)]) func = lambda bound: bound mapper = Mapper(fsig, func) assert mapper() == CallArguments(1)
def _build_signature(cls, obj, with_fields=True, with_id=True, with_self=True, all_optional=False): parameters = [] if with_self: parameters.append(_forge.arg("self")) if with_id: parameters.append(_forge.arg(obj._FIELD_ID, type=int)) if with_fields: # Recompute FIELDS object fields = obj._FIELDS if isinstance(fields, list): fields = {key: str for key in fields} if isinstance(fields, dict): fields = { key: (val, "") if (isinstance(val, type) or isinstance(val, _typing._GenericAlias)) else val for (key, val) in fields.items() } # Create forge parameters for (key, val) in fields.items(): if key in obj._FIELDS_READ_ONLY: continue if all_optional or not key in obj._FIELDS_REQUIRED: parameters.append( _forge.arg(key, type=val[0], default=_FORGE_VOID)) else: parameters.append(_forge.arg(key, type=val[0])) return _forge.FSignature(parameters=parameters)
def test_combo_raises(self, kwargs): """ Ensure that insertion with more than one of (index, before, or after) raises. """ with pytest.raises(TypeError) as excinfo: insert(forge.arg('x'), **kwargs) assert excinfo.value.args[0] == \ "expected 'index', 'before' or 'after' received multiple"
def test_combo_raises(self, kwargs): """ Ensure that ``index``, ``before``, or ``after`` can be passed, but not a combination """ with pytest.raises(TypeError) as excinfo: translocate(forge.arg('x'), **kwargs) assert excinfo.value.args[0] == \ "expected 'index', 'before' or 'after' received multiple"
def test_no_match_raises(self): """ Ensure that if selector doesn't find a match, an exception is rasied. """ rev = replace('i', forge.arg('a')) with pytest.raises(ValueError) as excinfo: rev.revise(FSignature()) assert excinfo.value.args[0] == \ "No parameter matched selector 'i'"
def test_revise_no_validation(self): """ Ensure no validation is performed on the revision """ rev = delete('x', raising=False) fsig = FSignature( [forge.arg('b'), forge.pos('a')], __validate_parameters__=False, ) assert rev.revise(fsig) is fsig
def test_revise_void_cls(self): """ Ensure that passing ``void`` as a ``default`` or ``type`` is passed through (distinguishing _void from void). """ in_ = FSignature([forge.arg('x')]) rev = modify('x', default=forge.void, type=forge.void) out_ = rev.revise(in_) assert out_.parameters['x'].default is forge.void assert out_.parameters['x'].type is forge.void
def test_revise_no_validation(self): """ Ensure no validation is performed on the revision """ rev = returns(int) fsig = FSignature( [forge.arg('b'), forge.pos('a')], __validate_parameters__=False, ) assert rev.revise(fsig).parameters == fsig.parameters
def lambdagen(obj): """create a generator of lambda functions""" for func_ in obj['__lambdagen__']: signature = [] for arg, arg_default in func_['params'].items(): signature.append(forge.arg(arg, default=deserialize(arg_default))) @forge.sign(*signature) def func(*args, **kwargs): """API function""" return deserialize(func_['result']) yield kamodofy(func)
def test_revise(self, selector, index, before, after, in_): """ Ensure that ``translocate``: - takes index - takes before as a selector value; i.e. value passed to ``findparam`` - takes after as a selector value; i.e. value passed to ``findparam`` """ # pylint: disable=R0913, too-many-arguments rev = translocate(selector, index=index, before=before, after=after) out_ = FSignature([ forge.arg('a'), forge.arg('_'), forge.arg('b'), forge.arg('c'), ]) if isinstance(out_, Exception): with pytest.raises(type(out_)) as excinfo: rev.revise(in_) assert excinfo.value.args[0] == out_.args[0] return assert rev.revise(in_) == out_
def test_get_context(self, has_context): """ Ensure the mapper retrieves the context value from arguments """ param = forge.ctx('param') \ if has_context \ else forge.arg('param') fsig = FSignature([param]) mapper = Mapper(fsig, lambda param: None) assert mapper.context_param == (param if has_context else None) kwargs = {'param': object()} ctx = mapper.get_context(kwargs) assert ctx == (kwargs['param'] if has_context else None)
def load_func(self, func_name): """loads a function signature""" signature = [] for arg, arg_default in self._defaults[func_name].items(): signature.append(forge.arg(arg, default=arg_default)) @forge.sign(*signature) def api_func(*args, **kwargs): """API function""" return self._call_func(func_name, **kwargs) api_func.__name__ = func_name api_func.__doc__ = "{}/{}".format(self._url_path, func_name) return api_func