Beispiel #1
0
def test_producing_php_types() -> None:
    from paradox.typing import (CrossAny, CrossCallable, CrossMap,
                                CrossNewType, CrossPythonOnlyType, CrossSet,
                                CrossTypeScriptOnlyType, dictof, listof, lit,
                                maybe, omittable, unflex, unionof)

    assert CrossAny().getPHPTypes()[0] is None
    assert CrossAny().getPHPTypes()[1] == 'mixed'
    assert unflex(str).getPHPTypes()[0] == 'string'
    assert unflex(str).getPHPTypes()[1] == 'string'
    assert unflex(int).getPHPTypes()[0] == 'int'
    assert unflex(int).getPHPTypes()[1] == 'int'
    assert unflex(bool).getPHPTypes()[0] == 'bool'
    assert unflex(bool).getPHPTypes()[1] == 'boolean'
    assert unflex(None).getPHPTypes()[0] is None
    assert unflex(None).getPHPTypes()[1] == 'null'
    assert lit('cheese').getPHPTypes()[0] == "string"
    assert lit('cheese').getPHPTypes()[1] == "string"

    with pytest.raises(NotImplementedError):
        # no way to support this in PHP
        assert omittable(int).getPHPTypes()

    assert CrossNewType('Widget').getPHPTypes()[0] == 'Widget'
    assert CrossNewType('Widget').getPHPTypes()[1] == 'Widget'
    assert maybe(str).getPHPTypes()[0] is None
    assert maybe(str).getPHPTypes()[1] == 'null|string'
    assert listof(int).getPHPTypes()[0] == 'array'
    assert listof(int).getPHPTypes()[1] == 'int[]'

    with pytest.raises(NotImplementedError):
        # no way to support this in PHP
        assert CrossSet(unflex(str)).getPHPTypes()

    # hell yeah PHP
    assert dictof(str, int).getPHPTypes()[0] == 'array'
    assert dictof(str, int).getPHPTypes()[1] == 'int[]'

    assert CrossMap(unflex(str), unflex(int)).getPHPTypes()[0] == 'Ds\\Map'
    assert CrossMap(unflex(str), unflex(int)).getPHPTypes()[1] == 'Ds\\Map'
    assert unionof(str, int).getPHPTypes()[0] is None
    assert unionof(str, int).getPHPTypes()[1] == 'string|int'
    c = CrossCallable([unflex(str), unflex(int)], unflex(bool))
    assert c.getPHPTypes()[0] is None
    assert c.getPHPTypes()[1] == 'callable'

    with pytest.raises(NotImplementedError):
        assert CrossPythonOnlyType('typing.Iterable[int]').getPHPTypes()

    with pytest.raises(NotImplementedError):
        assert CrossTypeScriptOnlyType('Promise<string>').getPHPTypes()

    # test that list of something complex changes to just 'mixed'
    assert listof(listof(unflex(int))).getPHPTypes()[0] == 'array'
    assert listof(listof(unflex(int))).getPHPTypes()[1] == 'mixed'  # more PHP greatness
Beispiel #2
0
def test_producing_typescript_types() -> None:
    from paradox.typing import (CrossAny, CrossCallable, CrossMap,
                                CrossNewType, CrossPythonOnlyType, CrossSet,
                                CrossTypeScriptOnlyType, dictof, listof, lit,
                                maybe, omittable, unflex, unionof)

    assert CrossAny().getTSType()[0] == 'any'
    assert unflex(str).getTSType()[0] == 'string'
    assert unflex(int).getTSType()[0] == 'number'
    assert unflex(bool).getTSType()[0] == 'boolean'
    assert unflex(None).getTSType()[0] == 'null'
    assert lit('cheese').getTSType()[0] == "'cheese'"
    assert omittable(int).getTSType()[0] == 'number | undefined'
    assert CrossNewType('Widget').getTSType()[0] == 'Widget'
    assert maybe(str).getTSType()[0] == 'string | null'
    assert listof(int).getTSType()[0] == 'number[]'
    assert CrossSet(unflex(str)).getTSType()[0] == 'Set<string>'
    assert dictof(str, int).getTSType()[0] == '{[k: string]: number}'
    assert CrossMap(unflex(str), unflex(int)).getTSType()[0] == 'Map<string, number>'
    assert unionof(str, int).getTSType()[0] == 'string | number'
    c = CrossCallable([unflex(str), unflex(int)], unflex(bool))
    assert c.getTSType()[0] == '(a: string, b: number) => boolean'

    with pytest.raises(NotImplementedError):
        assert CrossPythonOnlyType('typing.Iterable[int]').getTSType()

    promise = CrossTypeScriptOnlyType('Promise<string>')
    assert promise.getTSType()[0] == 'Promise<string>'

    # test that list of something complex changes to the Array<> syntax
    assert listof(promise).getTSType()[0] == 'Array<Promise<string>>'
Beispiel #3
0
def test_producing_python_types() -> None:
    from paradox.typing import (CrossAny, CrossCallable, CrossMap,
                                CrossNewType, CrossPythonOnlyType, CrossSet,
                                CrossTypeScriptOnlyType, dictof, listof, lit,
                                maybe, omittable, unflex, unionof)

    assert CrossAny().getPyType()[0] == 'typing.Any'
    assert unflex(str).getPyType()[0] == 'str'
    assert unflex(int).getPyType()[0] == 'int'
    assert unflex(bool).getPyType()[0] == 'bool'
    assert unflex(None).getPyType()[0] == 'None'
    assert lit('cheese').getPyType()[0] == "typing_extensions.Literal['cheese']"
    assert omittable(int).getPyType()[0] == 'typing.Union[int, builtins.ellipsis]'
    assert CrossNewType('Widget').getPyType()[0] == 'Widget'
    assert maybe(str).getPyType()[0] == 'typing.Optional[str]'
    assert listof(int).getPyType()[0] == 'typing.List[int]'
    assert CrossSet(unflex(str)).getPyType()[0] == 'typing.Set[str]'
    assert dictof(str, int).getPyType()[0] == 'typing.Dict[str, int]'
    assert CrossMap(unflex(str), unflex(int)).getPyType()[0] == 'typing.Mapping[str, int]'
    assert unionof(str, int).getPyType()[0] == 'typing.Union[str, int]'
    c = CrossCallable([unflex(str), unflex(int)], unflex(bool))
    assert c.getPyType()[0] == 'typing.Callable[[str, int], bool]'
    assert CrossPythonOnlyType('typing.Iterable[int]').getPyType()[0] == 'typing.Iterable[int]'

    with pytest.raises(NotImplementedError):
        assert CrossTypeScriptOnlyType('Promise<ApiFailure>').getPyType()
Beispiel #4
0
def _generateWrappers(
    dest: FileTS,
    classname: str,
    funcspecs: List[Tuple[str, FuncSpec]],
    adv: Advanced,
) -> None:
    cls = ClassSpec(
        classname,
        isabstract=True,
        tsexport=True,
        appendto=dest.contents,
    )

    # dispatch() function
    # FIXME: this should be protected, but we don't support that yet
    dispatchfn = cls.createMethod(
        'dispatch',
        CrossTypeScriptOnlyType('Promise<ApiFailure | any>'),
        isabstract=True,
    )
    # 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()))

    for name, funcspec in funcspecs:
        argnames = funcspec.getArgSpecs().keys()
        retspec = funcspec.getReturnSpec()
        # TODO: need to ensure that FunctionSpec writes this out as a Promise<ApiFailure, ...> due
        # to the isasync=True kwarg
        rettype = unionof(CrossNewType('ApiFailure'),
                          _generateCrossType(retspec, adv))

        fn = cls.createMethod(name, rettype, isasync=True)
        for argname, argspec in funcspec.getArgSpecs().items():
            fn.addPositionalArg(argname, _generateCrossType(argspec, adv))

        names = Names()

        argsdict = {argname: tsexpr(argname) for argname in argnames}
        fn.alsoDeclare('args', None, pandict(argsdict, CrossAny()))

        with fn.withRawTS() as ts:
            ts.rawline(f"const converter = (result: any) => {{")
            # verify that result matches the typespec for ret
            _generateConverter(ts, 'result', retspec, names, adv, '  ')
            ts.rawline(f'  return result;')
            ts.rawline(f'}};')
            ts.rawline(
                f"return await this.dispatch('{name}', args, converter);")
Beispiel #5
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