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

Support call_parse+docments+delegates compatability #398

Open
josiahls opened this issue Mar 17, 2022 · 0 comments
Open

Support call_parse+docments+delegates compatability #398

josiahls opened this issue Mar 17, 2022 · 0 comments

Comments

@josiahls
Copy link

josiahls commented Mar 17, 2022

My solution here is to modify delegates to output kwargs with Param type hints, thus propagating the docments through to call_parse. This solution is a tad ugly since cast_to_call_parse=True needs to be passed so that the delegates knows that it
is supposed to change everything to Param's.

Problem

call_parse does not work if using a delegates+docments combo. For example:

class A(object):
    "something"
    def __init__(self,
                 a:int=5 # This is a parameter
                ):
        pass

@call_parse
@delegates(A)
def some_callable(
        b:int=8, # A paramter that is defined in the call method.
        **kwargs
    ):
    "Some doc"
    pass

show_doc(A)
show_doc(some_callable)
print(anno_parser(some_callable).format_help())

Outputs:

TypeError                                 Traceback (most recent call last)
Input In [19], in <module>
     17 show_doc(A)
     18 show_doc(some_callable)
---> 19 anno_parser(some_callable)

File ~/.local/lib/python3.9/site-packages/fastcore/script.py:75, in anno_parser(func, prog, from_name)
     73     param = v.anno
     74     if not isinstance(param,Param): param = Param(v.docment, v.anno)
---> 75     param.set_default(v.default)
     76     p.add_argument(f"{param.pre}{k}", **param.kwargs)
     77 p.add_argument(f"--pdb", help=argparse.SUPPRESS, action='store_true')

File ~/.local/lib/python3.9/site-packages/fastcore/script.py:48, in Param.set_default(self, d)
     46     if d==inspect.Parameter.empty: self.opt = False
     47     else: self.default = d
---> 48 if self.default is not None: self.help += f" (default: {self.default})"

TypeError: unsupported operand type(s) for +=: 'NoneType' and 'str'

Slightly Dirty Solution

*also buggy, it doesnt handle default values very well

# export
import inspect
from inspect import Parameter
from fastcore.docments import docments
def delegates(to=None, keep=False, but=None, cast_to_call_parse=False):
    "Decorator: replace `**kwargs` in signature with params from `to`"
    if but is None: but = []
    
    def _f(f):
        if to is None: to_f,from_f = f.__base__.__init__,f.__init__
        else:          to_f,from_f = to.__init__ if isinstance(to,type) else to,f
        from_f = getattr(from_f,'__func__',from_f)
        to_f = getattr(to_f,'__func__',to_f)
        if hasattr(from_f,'__delwrap__'): return f
        sig = inspect.signature(from_f)
        sigd = dict(sig.parameters)
        k = sigd.pop('kwargs')
        s2 = {k:v for k,v in inspect.signature(to_f).parameters.items()
              if v.default != inspect.Parameter.empty and k not in sigd and k not in but}

        if cast_to_call_parse:
            for k,v in docments(to_f, full=True, returns=False).items():
                param = v.anno
                if v.default == inspect.Parameter.empty or k in sigd or k in but: continue
                if not isinstance(param,Param): param = Param(v.docment, v.anno)
                # if v.default is None
                param.set_default(ifnone(v.default,'None'))
                s2[k]=Parameter(k,Parameter.KEYWORD_ONLY,annotation=param)

        sigd.update(s2)
        if keep: sigd['kwargs'] = k
        else: from_f.__delwrap__ = to_f
        from_f.__signature__ = sig.replace(parameters=sigd.values())
        return f
    return _f

then if we set the flag:

@call_parse
@delegates(A,cast_to_call_parse=True)
def some_callable(
        b:int=8, # A paramter that is defined in the call method.
        **kwargs
    ):
    "Some doc"
    pass

show_doc(A)
show_doc(some_callable)
print(anno_parser(some_callable).format_help())

Outputs:

optional arguments:
  -h, --help  show this help message and exit
  --b B       A paramter that is defined in the call method. (default: 8)
  --a A       This is a parameter (default: 5) (default: 5)
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