Esempio n. 1
0
def _pan_nodef(val: Union[Pannable, NoDefault]) -> Optional[PanExpr]:
    if val is NO_DEFAULT:
        return None

    # NO_DEFAULT should be the only possible instance of NoDefault. This assertion is really just
    # to satisfy mypy
    assert not isinstance(val, NoDefault)
    return pan(val)
Esempio n. 2
0
 def withFor(
     self,
     assign: PanVar,
     expr: Pannable,
 ) -> 'Iterator[ForLoopBlock]':
     loop = ForLoopBlock(assign, pan(expr))
     self._statements.append(loop)
     yield loop
Esempio n. 3
0
def _generateWrappers(
    classname: str,
    funcspecs: List[Tuple[str, FuncSpec]],
    adv: Advanced,
    flavour: Flavour,
) -> ClassSpec:
    cls = ClassSpec(
        classname,
        isabstract=flavour == 'abstract',
        # TODO: we really should have a docstring for this
    )

    # add an dispatch() function that is used for all methods
    cls.alsoImportPy('typing')
    dispatchfn = cls.createMethod(
        '_dispatch',
        unionof(CrossNewType('ApiFailure'), CrossAny()),
        isabstract=flavour == 'abstract',
    )

    # the method that should be called
    dispatchfn.addPositionalArg('method', str)
    # a dict of params to pass to the method
    dispatchfn.addPositionalArg('params', dictof(str, CrossAny()))
    # converter will be called with the result of the method call.
    # It may modify result before returning it. It may raise a TypeError
    # if any part of result does not match the method's return type.
    dispatchfn.addPositionalArg('converter', CrossCallable([CrossAny()], CrossAny()))

    if flavour == 'requests':
        # TODO: finish this
        dispatchfn.alsoRaise(
            msg="TODO: perform the request using requests library"
        )
    else:
        assert flavour == 'abstract'
        # TODO: abstract function should get '...' body automatically?
        # dispatchfn.also('...')

    for name, funcspec in funcspecs:
        retspec = funcspec.getReturnSpec()

        # build a custom converter function for this method
        conv = FunctionSpec('_converter', CrossAny())
        v_result = conv.addPositionalArg('result', CrossAny())
        names = Names()

        try:
            filterblock = _getFilterBlock('result', '$DATA', retspec, names)
            conv.also(filterblock)
        except _FilterNotPossible:
            names.getSpecificName('converted', True)
            conv.also(_getConverterBlock('result', 'converted', '$DATA', retspec, names, adv))
            conv.alsoReturn(PanVar('converted', None))
        else:
            conv.alsoReturn(v_result)

        rettype = unionof(CrossNewType('ApiFailure'), _generateCrossType(retspec, adv))
        method = cls.createMethod(name, rettype)

        for argname, spec in funcspec.getArgSpecs().items():
            method.addPositionalArg(argname, _generateCrossType(spec, adv))

        v_args = PanVar('args', dictof(str, CrossAny()))
        argnames = DictBuilderStatement.fromPanVar(v_args)
        for n in funcspec.getArgSpecs().keys():
            # TODO: dataclasses aren't automatically JSON serializable, so we need to raise an
            # error if we try to generate a client that has a dataclass argument type
            argnames.addPair(n, False)
        method.also(argnames)

        method.blank()
        method.remark(
            'include [__dataclass__] in returned values so that we can rebuild dataclasses',
        )
        method.alsoAssign(v_args["__showdataclass__"], True)

        method.also(conv)

        method.alsoReturn(PanCall(
            'self._dispatch',
            pan(name),
            v_args,
            pyexpr('_converter'),
        ))

    # TODO: rather than writing to the file pointer, we should really be returning the class
    return cls
Esempio n. 4
0
def _getConverterBlock(
    var_or_prop: str,
    outname: str,
    label: str,
    spec: TypeSpec,
    names: Names,
    adv: Advanced,
) -> Statement:
    """
    Return a paradox Statement that will convert var_or_prop to the right type.

    The converted value is assigned to `outname`. A TypeError is raised by the generated code block
    if `var_or_prop` can't be converted.
    """
    assert outname != var_or_prop

    if not names.isAssignable(outname):
        raise Exception(
            f"Can't generate a converter to build {spec}"
            f" when {outname} is not assignable"
        )

    if isinstance(spec, ListTypeSpec):
        ret = Statements()

        # make sure the thing came back as a list
        with ret.withCond(pyexpr(f'not isinstance({var_or_prop}, list)')) as cond:
            cond.alsoRaise("TypeError", msg=f"{label} should be of type list")

        ret.alsoAssign(PanVar(outname, None), pyexpr('[]'))

        # add converts for the items - we know filter blocks aren't possible because if they were,
        # we wouldn't be using _getConverterBlock on a ListTypeSpec
        itemspec = spec.itemSpec
        itemvar = names.getNewName(var_or_prop, 'item', False)
        with ret.withFor(PanVar(itemvar, None), pyexpr(var_or_prop)) as loop:
            # TODO: also if we want to provide meaningful error messages, we really want to know
            # the idx of the item that was broken and include it in the error message
            itemvar2 = names.getNewName(var_or_prop, 'item_converted', True)
            loop.also(_getConverterBlock(
                itemvar,
                itemvar2,
                f"{label}[$n]",
                itemspec,
                names,
                adv,
            ))
            loop.also(pyexpr(f"{outname}.append({itemvar2})"))

        return ret

    if isinstance(spec, DataclassTypeSpec):
        if not adv.hasDataclass(spec.class_):
            raise Exception(
                f'Cannot generate a converter for unknown dataclass {spec.class_.__name__}')

        ret = Statements()
        ret.remark(f'try and build a {spec.class_.__name__} from {label}')
        ret.alsoAssign(PanVar(outname, None), PanCall(
            f'{spec.class_.__name__}.fromDict',
            PanVar(var_or_prop, None),
            pan(label),
        ))
        return ret

    if isinstance(spec, UnionTypeSpec):
        ret = Statements()

        # make a list of simple expressions that can be used to verify simple types quickly, and a
        # list of TypeSpecs for which simple expressions aren't possible
        notsimple: List[TypeSpec] = []
        simpleexprs: List[str] = []

        for vspec in spec.variants:
            nomatchexpr = _getTypeNoMatchExpr(var_or_prop, vspec)
            if nomatchexpr is None:
                notsimple.append(vspec)
            else:
                simpleexprs.append(nomatchexpr)

        # if they were all simple, we can use a single negative-if to rule out some invalid types
        if simpleexprs:
            innerstmt: Statements = ConditionalBlock(pyexpr(' and '.join(simpleexprs)))
            ret.also(innerstmt)
        else:
            innerstmt = ret

        if notsimple:
            # make sure we can assign a new value to var_or_prop
            if var_or_prop != 'result' and not names.isAssignable(var_or_prop):
                raise Exception(f"Overwriting {var_or_prop} won't work")

            # use a nested function for flow-control ... mostly so we can use 'return' statements
            # to break out of the function early if we find a matching type
            checkervar = names.getNewName('', 'value', True)
            checkername = names.getNewName('', 'checker', False)
            innercheck = FunctionSpec(checkername, CrossAny())
            innercheck.addPositionalArg(checkervar, CrossAny())
            innerstmt.also(innercheck)
            innerstmt.alsoAssign(PanVar(var_or_prop, None),
                                 PanCall(checkername, PanVar(var_or_prop, None)))
            innerstmt = innercheck

        for vspec in notsimple:
            innerstmt.remark('add a try/except for each vspec')
            with innerstmt.withTryBlock() as tryblock:
                filterblock = convertblock = None
                try:
                    filterblock = _getFilterBlock(checkervar, label, vspec, names)
                except _FilterNotPossible:
                    convertedvar = names.getNewName(checkervar, 'converted', True)
                    convertblock = _getConverterBlock(
                        checkervar,
                        convertedvar,
                        label,
                        vspec,
                        names,
                        adv,
                    )

                if filterblock:
                    tryblock.also(filterblock)
                    tryblock.alsoReturn(PanVar(checkervar, None))
                else:
                    assert convertblock is not None
                    tryblock.also(convertblock)
                    tryblock.alsoReturn(PanVar(convertedvar, None))
                with tryblock.withCatchBlock('TypeError') as catchblock:
                    catchblock.remark('ignore TypeError -contine on to next variant')
                    catchblock.also(pyexpr('pass'))
        innerstmt.alsoRaise("TypeError", msg=f"{var_or_prop} did not match any variant")
        return ret

    raise Exception(
        f"No code to generate a converter block for {var_or_prop} using {spec!r}"
    )
Esempio n. 5
0
 def alsoAssign(
     self,
     var: Union[PanVar, PanIndexAccess, PanKeyAccess],
     expr: Pannable,
 ) -> None:
     self._statements.append(AssignmentStatement(var, pan(expr)))
Esempio n. 6
0
 def alsoAppend(self, list_: Pannable, value: Pannable) -> None:
     self._statements.append(ListAppendStatement(pan(list_), pan(value)))
Esempio n. 7
0
 def alsoReturn(self, expr: Pannable) -> None:
     self._statements.append(ReturnStatement(pan(expr)))