Exemplo n.º 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
Exemplo n.º 2
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,
        )
Exemplo n.º 3
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)
Exemplo n.º 4
0
import forge
import aiohttp
from typing import Dict, List, Union

# RestClient signature decorators

GET_SIGNATURE = forge.compose(
    forge.copy(aiohttp.ClientSession.get),
    forge.modify("url", default=None),
    forge.returns(aiohttp.ClientResponse),
    forge.insert(
        [
            forge.kwo("parameters",
                      default={},
                      type=Dict[str, Union[str, List[str]]]),
            forge.kwo("headers", default={}, type=Dict[str, str]),
        ],
        before=lambda arg: arg.kind == forge.FParameter.VAR_KEYWORD,
    ),
)

MGET_SIGNATURE = forge.compose(
    forge.copy(aiohttp.ClientSession.get, exclude="url"),
    forge.returns(List[aiohttp.ClientResponse]),
    forge.insert(
        forge.pok(
            "urls",
            type=forge.fsignature(aiohttp.ClientSession.get)["url"].type,
            default=None,
        ),
        index=1,
Exemplo n.º 5
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)