Example #1
0
def methodizer(func=None, *, instance_params=()):
    """A decorator to get method versions of functions.

    :param func:
    :param instance_params:
    :return:

    >>> from py2http.decorators import methodizer
    >>>
    >>>
    >>> def f(a, b, x):
    ...     return x * (a + b)
    ...
    >>> def g(x, y=1):
    ...     return x * y
    ...
    >>> methodize = methodizer(instance_params=('x', 'non_existing_attr'))
    >>>
    >>> class A:
    ...     def __init__(self, x=0):
    ...         self.x = x
    ...
    ...     f = methodize(f)
    ...     g = methodize(g)
    ...
    >>>
    >>> a = A(x=3)
    >>> assert a.f(b=1, a=2) == 9
    >>> assert a.g() == 3
    >>> assert a.g(y=10) == 30

    """
    if func is None:
        return partial(methodizer, instance_params=instance_params)
    else:
        func = ch_func_to_all_pk(func)
        func_param_keys = signature(func).parameters.keys()
        self_argnames = func_param_keys & instance_params
        method_argnames = func_param_keys - self_argnames

        def method(self, **kwargs):
            kwargs_from_self = {a: getattr(self, a) for a in self_argnames}
            kwargs.update(kwargs_from_self)
            return func(**kwargs)

        set_signature_of_func(method, ['self'] + list(method_argnames))

        method.__name__ = func.__name__

        return method
Example #2
0
def mk_request_function(method_spec,
                        *,
                        function_kind='method',
                        dispatch=request):
    """
    Makes function that will make http requests for you, on your own terms.

    Specify what API you want to talk to, and how you want to talk to it (and it talk back to you), and
    get a function that does exactly that.

    Essentially, this factory function allows you to take an API specification, specify how you want it to
    relate to python objects (how to convert input arguments to API elements, and how to convert the response of
    the request, for instance), and get a method that is ready to be used.

    What does a method_spec contain?
    Well... consider first this. The core of this code is this:
    ```
        request_kwargs = dict(**method_spec['request_kwargs'])  # to make a copy
        ...  # a bunch more code that updates request_kwargs according to other keys of method_spec
        ...  # ... and some other debugging hooks
        r = request(**request_kwargs)  # call the request
        if 'output_trans' in method_spec:  # see if there's an output_trans function
            r = method_spec['output_trans'](r)  # ... if so, use it to extract what you want from the response
        return r
    ```
    So you can do almost everything you need with one single key: `'request_kwargs'` and `'output_trans'` alone.
    But there wouldn't be as much advantage over just calling requests if that's all there was to it,
    so we offer some other special keys to cover some of the common patterns.
        - 'method':
        - 'url_template': Specify the url, but with named placeholders: Example `'http://base.com/{user}/{action}'`.
        - 'json_arg_names': Specify the names of arguments of the function that should be put in the json load
        - 'debug': 'print_request_kwargs' or 'return_request_kwargs'
        - 'input_trans': Function applied to
        - 'output_trans': A function applied to response object to extract what we want to return.
        - 'wraps': A function whose signature we should use as the output's function's signature

    :param method_spec: Specification of how to convert arguments of the function that is being made to an http request.
    :return: A function.
        Note: I say "function", but the function is meant to be a method, so the function has a self as first argument.
        That argument is ignored.

    """
    original_spec = method_spec
    method_spec = method_spec.copy()  # make a copy
    method_spec['input_trans'] = method_spec.get('input_trans', None) or {}

    request_kwargs = method_spec.get('request_kwargs', {}).copy()
    method = method_spec.pop('method',
                             request_kwargs.pop('method', DFLT_REQUEST_METHOD))
    path_arg_names = _ensure_list(method_spec.get('path_arg_names', []))
    query_arg_names = _ensure_list(method_spec.get('query_arg_names', []))
    body_arg_names = _ensure_list(method_spec.get('body_arg_names', []))
    arg_specs = method_spec.get('arg_specs', [])
    formatted_arg_specs = [
        mk_param_spec_from_arg_schema(arg) for arg in arg_specs
    ]
    func_args = path_arg_names + query_arg_names + body_arg_names

    debug = method_spec.pop('debug', None)
    if 'debug' in method_spec:
        debug = method_spec['debug']

    output_trans = method_spec.pop('output_trans', None)
    if output_trans is None:
        response_type = method_spec.get('response_type', 'text/plain')
        if response_type == 'text/plain':
            output_trans = default_text_output_trans
        if response_type == 'application/json':
            output_trans = default_json_output_trans
        else:
            output_trans = default_content_output_trans

    wraps_func = method_spec.pop('wraps', None)

    # TODO: inject a signature, and possibly a __doc__ in this function
    def request_func(*args, **kwargs):
        def get_req_param_key():
            content_type = method_spec.get('content_type', 'text/plain')
            if content_type == 'text/plain':
                return 'data'
            if content_type == 'multipart/form-data':
                return 'files'
            if content_type == 'application/octet-stream':
                return 'stream'

        kwargs = dict(
            kwargs,
            **{argname: argval
               for argname, argval in zip(func_args, args)},
        )

        # convert argument types TODO: Not efficient. Might could be revised.
        for arg_name, converter in method_spec.get('input_trans', {}).items():
            if arg_name in kwargs:
                kwargs[arg_name] = converter(kwargs[arg_name])

        # making the request_kwargs ####################################################################################
        _request_kwargs = dict(**request_kwargs)  # to make a copy
        url = None
        if 'url_template' in method_spec:
            url_template = method_spec['url_template']
            # Check if the url template already have parameters, add them otherwise
            if query_arg_names and not re.search('^.*\?((.*=.*)(&?))+$',
                                                 url_template):
                url_arg_parts = [
                    f'{x}={{{x}}}' for x in query_arg_names if x in kwargs
                ]
                url_args = '&'.join(url_arg_parts)
                if url_args:
                    url_template += f'?{url_args}'
            param_kwargs = {
                k: v
                for k, v in kwargs.items()
                if k in path_arg_names or k in query_arg_names
            }
            encoded_kwargs = {
                k: urllib.parse.quote(v, safe='') if type(v) is str else v
                for k, v in param_kwargs.items()
            }
            url = url_template.format(**encoded_kwargs)
        elif 'url' in method_spec:
            url = method_spec['url']

        json = {k: v for k, v in kwargs.items() if is_jsonable(v)}
        _request_kwargs['json'] = json
        remaining_kwargs = {k: v for k, v in kwargs.items() if k not in json}
        if remaining_kwargs:
            req_param_key = get_req_param_key()
            _request_kwargs[req_param_key] = {
                k: v
                for k, v in remaining_kwargs.items() if k in body_arg_names
            }

        if debug is not None:
            if debug == 'print_request_kwargs':
                print(_request_kwargs)
            elif debug == 'return_request_kwargs':
                return _request_kwargs

        r = dispatch(method, url, **_request_kwargs)
        if callable(output_trans):
            return output_trans(r)
        return r

    if function_kind == 'method':
        _request_func = request_func

        def request_func(self, *args, **kwargs):
            return _request_func(*args, **kwargs)

    if wraps_func:
        return wraps(wraps_func)(request_func)
    else:
        if formatted_arg_specs:
            if function_kind == 'method':
                set_signature_of_func(request_func,
                                      ['self'] + formatted_arg_specs)
            elif function_kind == 'function':
                set_signature_of_func(request_func, formatted_arg_specs)

    request_func.original_spec = original_spec
    request_func.func_args = func_args
    request_func.debug = debug
    request_func.method_spec = method_spec
    funcname = method_spec.get('method_name', None)
    if funcname:
        request_func.__name__ = funcname
    docstring = method_spec.get('docstring', None)
    if docstring:
        request_func.__doc__ = docstring

    assert callable(
        output_trans
    ), f'output_trans {output_trans} is not callable, try again'
    return request_func
Example #3
0
def mk_request_function(method_spec):
    """
    Makes function that will make http requests for you, on your own terms.

    Specify what API you want to talk to, and how you want to talk to it (and it talk back to you), and
    get a function that does exactly that.

    Essentially, this factory function allows you to take an API specification, specify how you want it to
    relate to python objects (how to convert input arguments to API elements, and how to convert the response of
    the request, for instance), and get a method that is ready to be used.

    :param method_spec: Specification of how to convert arguments of the function that is being made to an http request.
    :return: A function.
        Note: I say "function", but the function is meant to be a method, so the function has a self as first argument.
        That argument is ignored.

    """
    # defaults
    method_spec = method_spec.copy()
    method_spec['request_kwargs'] = method_spec.get('request_kwargs', {})
    method_spec['request_kwargs']['method'] = method_spec[
        'request_kwargs'].get('method', 'GET')
    arg_order = _ensure_list(method_spec.get('args', []))

    # TODO: inject a signature, and possibly a __doc__ in this function
    def request_func(self, *args, **kwargs):

        # absorb args in kwargs
        if len(args) > len(arg_order):
            raise ValueError(
                f"The number ({len(args)}) of unnamed arguments was greater than "
                f"the number ({len(arg_order)}of specified arguments in arg_order"
            )

        kwargs = dict(
            kwargs,
            **{argname: argval
               for argname, argval in zip(arg_order, args)})

        # convert argument types TODO: Not efficient. Might could be revised.
        for arg_name, converter in method_spec.get('input_trans', {}).items():
            if arg_name in kwargs:
                kwargs[arg_name] = converter(kwargs[arg_name])

        json_data = {}
        for arg_name in method_spec.get('json_arg_names', []):
            if arg_name in kwargs:
                json_data[arg_name] = kwargs.pop(arg_name)

        # making the request_kwargs ####################################################################################
        request_kwargs = method_spec['request_kwargs']
        if 'url_template' in method_spec:
            request_kwargs['url'] = method_spec['url_template'].format(
                **kwargs)
        elif 'url' in method_spec:
            request_kwargs['url'] = method_spec['url']

        if json_data:
            request_kwargs['json'] = json_data

        if 'debug' in method_spec:
            debug = method_spec['debug']
            if debug == 'print_request_kwargs':
                print(request_kwargs)
            elif debug == 'return_request_kwargs':
                return request_kwargs

        r = request(**request_kwargs)
        if 'output_trans' in method_spec:
            r = method_spec['output_trans'](r)
        return r

    if 'wraps' in method_spec:
        return wraps(method_spec['wraps'])(request_func)
    else:
        all_args = method_spec.get('args', []) + method_spec.get(
            'json_arg_names', [])
        if all_args:
            set_signature_of_func(request_func, ['self'] + all_args)

    return request_func
Example #4
0
        register_cli_method(openapi_spec, method, expected_auth_kwargs)
        for methodname, method in client_details.__dict__.items()
        if getattr(method, 'method_spec', None)
    ]
    parser = argh.ArghParser()
    parser.add_commands(cli_methods)
    return parser


def dispatch_cli(*args, **kwargs):
    """Makes a CLI parser with mk_cli and then dispatches it (see documentation of mk_cli)"""
    parser = mk_cli(*args, **kwargs)
    parser.dispatch()


set_signature_of_func(dispatch_cli, signature(mk_cli))


def register_cli_method(
    openapi_spec: dict,
    client_method: Callable,
    expected_auth_kwargs: Iterable[str] = None,
    config_filename: str = DFLT_CONFIG_FILENAME,
    profile: str = '',
):
    """Creates a CLI-friendly function to instantiate an HttpClient with appropriate authentication
    arguments and call a particular method of the client instance

    :param openapi_spec: The OpenAPI spec used to make the client
    :param client_method: The instance method to wrap
    :param expected_auth_kwargs: A list of authentication kwargs that the CLI should ask for
Example #5
0
def mk_request_function(method_spec):
    """
    Makes function that will make http requests for you, on your own terms.

    Specify what API you want to talk to, and how you want to talk to it (and it talk back to you), and
    get a function that does exactly that.

    Essentially, this factory function allows you to take an API specification, specify how you want it to
    relate to python objects (how to convert input arguments to API elements, and how to convert the response of
    the request, for instance), and get a method that is ready to be used.

    What does a method_spec contain?
    Well... consider first this. The core of this code is this:
    ```
        request_kwargs = dict(**method_spec['request_kwargs'])  # to make a copy
        ...  # a bunch more code that updates request_kwargs according to other keys of method_spec
        ...  # ... and some other debugging hooks
        r = request(**request_kwargs)  # call the request
        if 'output_trans' in method_spec:  # see if there's an output_trans function
            r = method_spec['output_trans'](r)  # ... if so, use it to extract what you want from the response
        return r
    ```
    So you can do almost everything you need with one single key: `'request_kwargs'` and `'output_trans'` alone.
    But there wouldn't be as much advantage over just calling requests if that's all there was to it,
    so we offer some other special keys to cover some of the common patterns.
        - 'method':
        - 'url_template': Specify the url, but with named placeholders: Example `'http://base.com/{user}/{action}'`.
        - 'json_arg_names': Specify the names of arguments of the function that should be put in the json load
        - 'debug': 'print_request_kwargs' or 'return_request_kwargs'
        - 'input_trans': Function applied to
        - 'output_trans': A function applied to response object to extract what we want to return.
        - 'wraps': A function whose signature we should use as the output's function's signature

    :param method_spec: Specification of how to convert arguments of the function that is being made to an http request.
    :return: A function.
        Note: I say "function", but the function is meant to be a method, so the function has a self as first argument.
        That argument is ignored.

    """
    # defaults
    original_spec = method_spec
    method_spec = method_spec.copy()  # make a copy
    request_kwargs = method_spec.get('request_kwargs', {}).copy()
    method = method_spec.pop('method',
                             request_kwargs.pop('method', DFLT_REQUEST_METHOD))
    func_args = _ensure_list(method_spec.get('args', [])) + _ensure_list(
        method_spec.get('json_arg_names', []))

    debug = method_spec.pop('debug', None)
    if 'debug' in method_spec:
        debug = method_spec['debug']

    output_trans = method_spec.pop('output_trans', lambda x: x)

    wraps_func = method_spec.pop('wraps', None)

    # TODO: inject a signature, and possibly a __doc__ in this function
    def request_func(self, *args, **kwargs):

        kwargs = dict(
            kwargs,
            **{argname: argval
               for argname, argval in zip(func_args, args)})

        # convert argument types TODO: Not efficient. Might could be revised.
        for arg_name, converter in method_spec.get('input_trans', {}).items():
            if arg_name in kwargs:
                kwargs[arg_name] = converter(kwargs[arg_name])

        json_data = {}
        for arg_name in method_spec.get('json_arg_names', []):
            if arg_name in kwargs:
                json_data[arg_name] = kwargs.pop(arg_name)

        # making the request_kwargs ####################################################################################
        _request_kwargs = dict(**request_kwargs)  # to make a copy
        url = None
        if 'url_template' in method_spec:
            url = method_spec['url_template'].format(**kwargs)
        elif 'url' in method_spec:
            url = method_spec.pop('url', None)

        if json_data:
            _request_kwargs['json'] = json_data

        if debug is not None:
            if debug == 'print_request_kwargs':
                print(_request_kwargs)
            elif debug == 'return_request_kwargs':
                return _request_kwargs

        r = request(method, url, **_request_kwargs)
        return output_trans(r)

    if wraps_func:
        return wraps(wraps_func)(request_func)
    else:
        if func_args:
            set_signature_of_func(request_func, ['self'] + func_args)

    request_func.original_spec = original_spec
    request_func.func_args = func_args
    request_func.debug = debug
    request_func.method_spec = method_spec

    return request_func