Example #1
0
class TestSort:
    @pytest.mark.parametrize(('in_', 'sortkey', 'expected'), [
        pytest.param(
            [forge.arg('b'), forge.arg('a')],
            None,
            [forge.arg('a'), forge.arg('b')],
            id='lexicographical',
        ),
        pytest.param(
            [forge.arg('a', default=None),
             forge.arg('b')],
            None,
            [forge.arg('b'), forge.arg('a', default=None)],
            id='default',
        ),
        pytest.param(
            [
                forge.vkw('e'),
                forge.kwo('d'),
                forge.vpo('c'),
                forge.pok('b'),
                forge.pos('a'),
            ],
            None,
            [
                forge.pos('a'),
                forge.pok('b'),
                forge.vpo('c'),
                forge.kwo('d'),
                forge.vkw('e'),
            ],
            id='kind',
        ),
        pytest.param(
            [forge.arg('x', 'b'), forge.arg('y', 'a')],
            lambda param: param.interface_name,
            [forge.arg('y', 'a'), forge.arg('x', 'b')],
            id='sortkey_interface_name',
        ),
        pytest.param(
            [forge.vpo('a'), forge.vpo('b')],
            None,
            [forge.vpo('a'), forge.vpo('b')],
            id='novalidate',
        ),
    ])
    def test_revise(self, in_, sortkey, expected):
        """
        Ensure that parameter sorting:
        - doesn't validate the signature
        - by default sorts by (kind, has-default, name)
        - takes advantage of user-supplied sortkey
        """
        rev = sort(sortkey)
        in_ = FSignature(in_, __validate_parameters__=False)
        expected = FSignature(expected, __validate_parameters__=False)
        assert rev.revise(in_) == expected
Example #2
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,
     )
Example #3
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,
     )
Example #4
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_
Example #5
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,
        )
Example #6
0
def get_python_routine(routine):
    f_routine = get_fortran_routine(routine.name)

    @forge.sign(*[forge.pos(arg.name) for arg in routine.args[:-2]
                  ]  # skip errmsg, errflg args as we treat those internally
                )
    def python_routine(**kwargs):
        fortran_args = []
        logging.debug(f"calling routine {routine.name}")
        for signature in routine.args:
            if signature.name not in ('errmsg', 'errflg'):
                arg = kwargs[signature.name]
                if isinstance(arg, np.ndarray):
                    check_dimensions(arg, signature, kwargs)
                    check_type(arg, signature)
                    fortran_args.append(numpy_pointer(arg))
                else:
                    raise NotImplementedError()
        f_routine(*fortran_args, ERRMSG, numpy_pointer(ERRFLG))
        if ERRFLG != 0:
            raise CCPPError(f"{routine.name}: {ERRMSG}")
        logging.debug(f"completed routine {routine.name}")

    python_routine.__name__ = routine.name
    return python_routine
Example #7
0
 def test__repr__(self):
     """
     Ensure the mapper is pretty printable with ``FSignature`` and
     ``inspect.Signature``
     """
     fsig = FSignature([forge.pos('a', 'b')])
     callable_ = lambda *, b: None
     mapper = Mapper(fsig, callable_)
     assert repr(mapper) == '<Mapper (a, /) => (*, b)>'
Example #8
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
Example #9
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
Example #10
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_
Example #11
0
    def test_revise(self, revision):
        """
        Ensure that ``modify`` appropriately revises every attribute of a
        parameter.
        """
        in_param = forge.pos('a')
        out_param = in_param.replace(**revision)
        assert in_param != out_param  # ensure we've got a good test setup

        rev = modify('a', **revision)
        assert rev.revise(FSignature([in_param])) == FSignature([out_param])
Example #12
0
 def test__call__validates(self):
     """
     Ensure that `__call__` validates the signature. Notable because
     `revise` methods typically don't validate.
     """
     rev = Revision()
     rev.revise = lambda prev: FSignature(
         [forge.arg('b'), forge.pos('a')],
         __validate_parameters__=False,
     )
     with pytest.raises(SyntaxError) as excinfo:
         rev(lambda a, b: None)
     assert excinfo.value.args[0] == (
         "'a' of kind 'POSITIONAL_ONLY' follows 'b' of kind "
         "'POSITIONAL_OR_KEYWORD'")
Example #13
0
    def _initialize(self, instance):
        self.request = instance.request

        available_parameters = {
            # Json body is only available for POST/PUT
            ('create', 'update'):
            self._params['body'],
            # Query parameter is only available for GET/DELETE
            ('get', 'list', 'delete'):
            self._params['query'],
        }

        for method, path in self._paths.items():
            # Path parameter would be postional argument
            sigs = [forge.pos(f) for f in path.fields]

            for methods, params in available_parameters.items():
                if method in methods:
                    # Query/Body parameter would be keyword only argument
                    sigs.extend(
                        forge.kwo(
                            param.name, default=param.default, type=param.type)
                        for param in params)

            # Any other keyword argument would be matched to 'kwargs'
            sigs.append(forge.vkw('kwargs'))
            setattr(self, method, forge.sign(*sigs)(getattr(self, method)))

        for method in list(self.methods):

            def unsupported(*args, **kwargs):
                raise NotImplementedError(f"'{self._name}' has no method "
                                          f"what you called")

            if method not in self._paths:
                setattr(self, method, unsupported)
                self.methods.remove(method)
Example #14
0
def create_method(client, params, body, method_info):
    _sig_url_params = []
    _sig_params_req = []
    _sig_params_opt = []
    _sig_body_req = []
    _sig_body_opt = []

    for _key, _url_param in method_info['url_parameters'].items():
        _sig_url_params.append(forge.arg(name=_key, type=_url_param['type']))

    if params['data']:
        for _key, _param_item in params['data'].items():
            if _param_item['required']:
                _sig_params_req.append(
                    forge.arg(name=_key, type=_param_item['type']))
            else:
                _sig_params_opt.append(
                    forge.kwo(name=_key,
                              type=_param_item['type'],
                              default=None))

    if body['data']:
        for _key, _body_item in body['data'].items():
            if _body_item['required']:
                _sig_body_req.append(
                    forge.arg(name=_key, type=_body_item['type']))
            else:
                _sig_body_opt.append(
                    forge.kwo(name=_key, type=_body_item['type'],
                              default=None))

    async def inner_method(self, **kwargs):
        # Verify signature
        endpoint_signature = {}
        for url_param, url_param_info in method_info['url_parameters'].items():
            if url_param not in kwargs:
                raise SyntaxError(
                    f'Argument {url_param!r} is a required keyword argument.')

            if 'validator' in url_param_info:
                tested = url_param_info['validator'](kwargs[url_param])
                if not tested:
                    # TODO: less generic error messages by abstracting this to the endpoint data structure
                    raise ValueError(
                        f'Supplied argument {url_param!r} failed to validate')

            endpoint_signature[url_param] = kwargs[url_param]
        endpoint = method_info['endpoint'].format(**endpoint_signature)

        # Verify params
        # TODO: DRY
        params_signature = []
        for param in params['required']:
            item = None

            if param not in kwargs:
                if 'default' not in params['data'][param]:
                    raise SyntaxError(
                        f'Parameter {param!r} is a required keyword argument')
                else:
                    item = params['data'][param]

            if not item:
                item = kwargs[param]

            if 'validator' in params['data'][param]:
                tested = params['data'][param]['validator'](item)
                if not tested:
                    # TODO: less generic error messages by abstracting this to the endpoint data structure
                    raise ValueError(
                        f'Supplied argument {param!r} failed to validate')

            if '_internal_name' in params['data'][param]:
                params_signature.append(
                    (params['data'][param]['_internal_name'],
                     format_parameter_value(item)))
            else:
                params_signature.append((param, format_parameter_value(item)))

        for param in params['optional']:
            if param in kwargs and kwargs[param] is not None:
                if 'validator' in params['data'][param]:
                    tested = params['data'][param]['validator'](kwargs[param])
                    if not tested:
                        # TODO: less generic error messages by abstracting this to the endpoint data structure
                        raise ValueError(
                            f'Supplied argument {param!r} failed to validate')

                if '_internal_name' in params['data'][param]:
                    params_signature.append(
                        (params['data'][param]['_internal_name'],
                         format_parameter_value(kwargs[param])))
                else:
                    params_signature.append(
                        (param, format_parameter_value(kwargs[param])))

        # Verify body
        # TODO: DRY
        body_signature = {}
        for key in body['required']:
            item = None
            if key not in kwargs:
                if 'default' not in body['data'][key]:
                    raise SyntaxError(
                        f'Body key {key!r} is a required keyword argument')
                else:
                    item = body['data'][key]['default']

            if not item:
                item = kwargs[key]

            if 'validator' in body['data'][key]:
                tested = body['data'][key]['validator'](item)
                if not tested:
                    # TODO: less generic error messages by abstracting this to the endpoint data structure
                    raise ValueError(
                        f'Supplied argument {key!r} failed to validate')

            if '_internal_name' in body['data'][key]:
                body_signature[body['data'][key]['_internal_name']] = item if method_info['body_type'] == 'json' else \
                    format_parameter_value(item)
            else:
                body_signature[key] = item if method_info['body_type'] == 'json' else \
                    format_parameter_value(item)

        for key in body['optional']:
            if key in kwargs and kwargs[key] is not None:
                if 'validator' in body['data'][key]:
                    tested = body['data'][key]['validator'](kwargs[key])
                    if not tested:
                        # TODO: less generic error messages by abstracting this to the endpoint data structure
                        raise ValueError(
                            f'Supplied argument {key!r} failed to validate')

                if '_internal_name' in body['data'][key]:
                    body_signature[body['data'][key]['_internal_name']] = kwargs[key] if \
                        method_info['body_type'] == 'json' else \
                        format_parameter_value(kwargs[key])
                else:
                    body_signature[key] = kwargs[key] if method_info['body_type'] == 'json' else \
                        format_parameter_value(kwargs[key])

        if method_info['http_method'] in ['POST', 'PUT', 'PATCH']:
            # Have a body
            if method_info['body_type'] == 'kv':
                resp = await self.signed_request(
                    method_info['http_method'],
                    endpoint,
                    params=params_signature,
                    data=body_signature,
                    headers={'content-type': method_info['content_type']})
            elif method_info['body_type'] == 'json':
                resp = await self.signed_request(
                    method_info['http_method'],
                    endpoint,
                    params=params_signature,
                    json=body_signature,
                    headers={'content-type': method_info['content_type']})
            else:
                raise RuntimeError(
                    f'Unknown body type {method_info["body_type"]!r} in method {method_info["method_name"]!r}'
                )
        elif method_info['http_method'] in ['GET', 'DELETE']:
            # No body for these methods, nor a specific content-type
            resp = await self.signed_request(method_info['http_method'],
                                             endpoint,
                                             params=params_signature)
        else:
            raise NotImplementedError(
                f'Unsupported HTTP verb {method_info["http_method"]!r}.')

        return resp

    revised = forge.sign(
        forge.pos(name='self'),
        *_sig_url_params,
        *_sig_params_req,
        *_sig_body_req,
        *_sig_params_opt,
        *_sig_body_opt,
    )(inner_method)
    revised.__name__ = method_info['method_name']

    revised.__doc__ = generate_docstring(params, body, method_info)
    setattr(client, method_info['method_name'], revised)