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
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 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 = 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 _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 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
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)>'
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_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 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(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])
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'")
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)
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)