Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FunctionBuilder.from_func not working #301

Open
thorwhalen opened this issue Mar 10, 2022 · 1 comment
Open

FunctionBuilder.from_func not working #301

thorwhalen opened this issue Mar 10, 2022 · 1 comment

Comments

@thorwhalen
Copy link
Contributor

FunctionBuilder.from_func doesn't seem to work.

def foo(a, b=2):
    return a / b
    
f = FunctionBuilder.from_func(foo).get_func()
assert f(20) == foo(20) == 10.0  # fails

Actually f(20) returns None.
I looked into the code and see that the body isn't extracted and specified when making the function.

@thorwhalen
Copy link
Contributor Author

thorwhalen commented Mar 10, 2022

I'd propose something like the following. Should I do a pull request?
If so, might you have your own preferred get_function_body you'd like me to use?

from boltons.funcutils import FunctionBuilder, _inspect_iscoroutinefunction
import functools
    
class MyFunctionBuilder(FunctionBuilder):
    @classmethod
    def from_func(cls, func):
        """Create a new FunctionBuilder instance based on an existing
        function. The original function will not be stored or
        modified.
        """
        # TODO: copy_body? gonna need a good signature regex.
        # TODO: might worry about __closure__?
        if not callable(func):
            raise TypeError('expected callable object, not %r' % (func,))

        if isinstance(func, functools.partial):
            if _IS_PY2:
                raise ValueError('Cannot build FunctionBuilder instances from partials in python 2.')
            kwargs = {'name': func.func.__name__,
                      'doc': func.func.__doc__,
                      'module': getattr(func.func, '__module__', None),  # e.g., method_descriptor
                      'annotations': getattr(func.func, "__annotations__", {}),
                      'body': get_function_body(func.func),  # <-- NEW: add body
                      'dict': getattr(func.func, '__dict__', {})}
        else:
            kwargs = {'name': func.__name__,
                      'doc': func.__doc__,
                      'module': getattr(func, '__module__', None),  # e.g., method_descriptor
                      'annotations': getattr(func, "__annotations__", {}),
                      'body': get_function_body(func),  # <-- NEW: add body
                      'dict': getattr(func, '__dict__', {})}

        kwargs.update(cls._argspec_to_dict(func))

        if _inspect_iscoroutinefunction(func):
            kwargs['is_async'] = True

        return cls(**kwargs)


# We'll need a `get_function_body` function for this, something like:
import inspect
from itertools import dropwhile

def get_function_body(func):
    source_lines = next(iter(inspect.getsourcelines(func)), None)
    if source_lines is None:
        raise ValueError(f"No source lines found func: {func}")
    source_lines = dropwhile(lambda x: x.startswith('@'), source_lines)
    def_line = next(source_lines).strip()
    if def_line.startswith('def ') and def_line.endswith(':'):
        first_line = next(source_lines)
        indentation = len(first_line) - len(first_line.lstrip())
        return ''.join([first_line[indentation:]] + [line[indentation:] for line in source_lines])
    else:
        return def_line.rsplit(':')[-1].strip()
    
f = MyFunctionBuilder.from_func(foo).get_func()
assert f(20) == foo(20) == 10.0  # works now

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant