Beispiel #1
0
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
Beispiel #2
0
 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])
Beispiel #3
0
 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])
Beispiel #4
0
 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])
Beispiel #5
0
 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,
     )
Beispiel #6
0
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
Beispiel #7
0
 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,
     )
Beispiel #8
0
 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_
Beispiel #9
0
 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)
Beispiel #11
0
    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_
Beispiel #12
0
    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')])
Beispiel #13
0
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
Beispiel #14
0
    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]
Beispiel #15
0
    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,
        )
Beispiel #16
0
    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)
Beispiel #17
0
 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'"
Beispiel #18
0
 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'"
Beispiel #19
0
 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)
Beispiel #20
0
    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)
Beispiel #21
0
 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"
Beispiel #22
0
 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"
Beispiel #23
0
 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'"
Beispiel #24
0
 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
Beispiel #25
0
 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
Beispiel #26
0
 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
Beispiel #27
0
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)
Beispiel #28
0
    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_
Beispiel #29
0
    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)
Beispiel #30
0
    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