示例#1
0
def test_get_Literal_type_spec() -> None:
    from bifrostrpc.typing import Advanced
    from bifrostrpc.typing import getTypeSpec
    from bifrostrpc.typing import LiteralTypeSpec

    # need type: ignore here because mypy can't work out what Literal is due to
    # import fallback mechanism above
    Five = NewType('Five', Literal[5])  # type: ignore
    Hello = NewType('Hello', Literal["hello"])  # type: ignore
    adv = Advanced()
    adv.addNewType(Five)
    adv.addNewType(Hello)

    ts = getTypeSpec(Literal[5], adv)
    assert isinstance(ts, LiteralTypeSpec)
    assert ts.expected == 5
    assert ts.expectedType is int

    ts = getTypeSpec(Five, adv)
    assert isinstance(ts, LiteralTypeSpec)
    assert ts.expected == 5
    assert ts.expectedType is int

    ts = getTypeSpec(Hello, adv)
    assert isinstance(ts, LiteralTypeSpec)
    assert ts.expected == "hello"
    assert ts.expectedType is str
示例#2
0
def test_get_str_type_spec() -> None:
    from bifrostrpc.typing import Advanced
    from bifrostrpc.typing import getTypeSpec
    from bifrostrpc.typing import ScalarTypeSpec

    adv = Advanced()

    ts = getTypeSpec(str, adv)
    assert isinstance(ts, ScalarTypeSpec)
    assert ts.scalarType is str
    assert ts.originalType is str
    assert ts.typeName == 'str'

    MyStr = NewType('MyStr', str)
    adv.addNewType(MyStr)

    ts = getTypeSpec(MyStr, adv)
    assert isinstance(ts, ScalarTypeSpec)
    assert ts.scalarType is str
    assert ts.originalType is MyStr
    assert ts.typeName == 'MyStr'

    MyStr2 = NewType('MyStr2', MyStr)
    adv.addNewType(MyStr2)

    ts = getTypeSpec(MyStr2, adv)
    assert isinstance(ts, ScalarTypeSpec)
    assert ts.scalarType is str
    assert ts.originalType is MyStr2
    assert ts.typeName == 'MyStr2'
示例#3
0
def test_get_int_type_spec() -> None:
    from bifrostrpc.typing import Advanced
    from bifrostrpc.typing import getTypeSpec
    from bifrostrpc.typing import ScalarTypeSpec

    adv = Advanced()

    ts = getTypeSpec(int, adv)
    assert isinstance(ts, ScalarTypeSpec)
    assert ts.scalarType is int
    assert ts.originalType is int
    assert ts.typeName == 'int'

    MyInt = NewType('MyInt', int)
    adv.addNewType(MyInt)

    ts = getTypeSpec(MyInt, adv)
    assert isinstance(ts, ScalarTypeSpec)
    assert ts.scalarType is int
    assert ts.originalType is MyInt
    assert ts.typeName == 'MyInt'

    MyInt2 = NewType('MyInt2', MyInt)
    adv.addNewType(MyInt2)

    ts = getTypeSpec(MyInt2, adv)
    assert isinstance(ts, ScalarTypeSpec)
    assert ts.scalarType is int
    assert ts.originalType is MyInt2
    assert ts.typeName == 'MyInt2'
示例#4
0
def test_get_List_type_spec() -> None:
    from bifrostrpc import TypeNotSupportedError
    from bifrostrpc.typing import Advanced
    from bifrostrpc.typing import ListTypeSpec
    from bifrostrpc.typing import ScalarTypeSpec
    from bifrostrpc.typing import getTypeSpec

    # test handling of a simple List[int]
    ts = getTypeSpec(List[int], Advanced())
    assert isinstance(ts, ListTypeSpec)

    assert isinstance(ts.itemSpec, ScalarTypeSpec)
    assert ts.itemSpec.scalarType is int
    assert ts.itemSpec.originalType is int
    assert ts.itemSpec.typeName == 'int'

    # test handling of a more complex List[List[CustomType]]
    MyStr = NewType('MyStr', str)
    MyStr2 = NewType('MyStr2', MyStr)
    adv = Advanced()
    adv.addNewType(MyStr)
    adv.addNewType(MyStr2)
    ts = getTypeSpec(List[List[MyStr2]], adv)
    assert isinstance(ts, ListTypeSpec)
    assert isinstance(ts.itemSpec, ListTypeSpec)
    assert isinstance(ts.itemSpec.itemSpec, ScalarTypeSpec)
    assert ts.itemSpec.itemSpec.scalarType is str
    assert ts.itemSpec.itemSpec.originalType is MyStr2
    assert ts.itemSpec.itemSpec.typeName == 'MyStr2'

    UserID = NewType('UserID', int)
    Users = NewType('Users', List[UserID])
    adv = Advanced()
    adv.addNewType(UserID)
    adv.addNewType(Users)

    # TODO: we don't yet support a NewType wrapping a List like this
    with raises(TypeNotSupportedError):
        ts = getTypeSpec(Users, adv)
        assert isinstance(ts, ListTypeSpec)
        assert isinstance(ts.itemSpec, ListTypeSpec)
        assert isinstance(ts.itemSpec.itemSpec, ScalarTypeSpec)
        assert ts.itemSpec.itemSpec.scalarType is UserID
        assert ts.itemSpec.itemSpec.originalType is int
        assert ts.itemSpec.itemSpec.typeName == 'UserID'
示例#5
0
def test_get_Dict_type_spec() -> None:
    from bifrostrpc import TypeNotSupportedError
    from bifrostrpc.typing import Advanced
    from bifrostrpc.typing import DictTypeSpec
    from bifrostrpc.typing import ScalarTypeSpec
    from bifrostrpc.typing import getTypeSpec

    # test handling of a simple Dict[str, int]
    ts = getTypeSpec(Dict[str, int], Advanced())
    assert isinstance(ts, DictTypeSpec)

    assert isinstance(ts.keySpec, ScalarTypeSpec)
    assert ts.keySpec.scalarType is str
    assert ts.keySpec.originalType is str
    assert ts.keySpec.typeName == 'str'

    assert isinstance(ts.valueSpec, ScalarTypeSpec)
    assert ts.valueSpec.scalarType is int
    assert ts.valueSpec.originalType is int
    assert ts.valueSpec.typeName == 'int'

    # test handling of a more complex Dict[str, Dict[str, CustomType]]
    MyStr = NewType('MyStr', str)
    MyStr2 = NewType('MyStr2', MyStr)
    adv = Advanced()
    adv.addNewType(MyStr)
    adv.addNewType(MyStr2)
    ts = getTypeSpec(Dict[str, Dict[str, MyStr2]], adv)
    assert isinstance(ts, DictTypeSpec)
    assert isinstance(ts.keySpec, ScalarTypeSpec)
    assert isinstance(ts.valueSpec, DictTypeSpec)
    assert isinstance(ts.valueSpec.keySpec, ScalarTypeSpec)
    assert isinstance(ts.valueSpec.valueSpec, ScalarTypeSpec)
    assert ts.keySpec.scalarType is str
    assert ts.keySpec.originalType is str
    assert ts.keySpec.typeName == 'str'
    assert ts.valueSpec.keySpec.scalarType is str
    assert ts.valueSpec.keySpec.originalType is str
    assert ts.valueSpec.keySpec.typeName == 'str'
    assert ts.valueSpec.valueSpec.scalarType is str
    assert ts.valueSpec.valueSpec.originalType is MyStr2
    assert ts.valueSpec.valueSpec.typeName == 'MyStr2'

    with raises(TypeNotSupportedError):
        getTypeSpec(Dict[MyStr2, str], adv)
示例#6
0
def test_get_Union_type_spec() -> None:
    from bifrostrpc.typing import Advanced
    from bifrostrpc.typing import getTypeSpec

    # test handling of a simple Union[str, int, None]
    _assert_union_variants(
        getTypeSpec(Union[str, int, None], Advanced()),
        is_null_spec,
        ScalarTester(str, str, 'str'),
        ScalarTester(int, int, 'int'),
    )

    # test handling of Optional[]
    _assert_union_variants(
        getTypeSpec(Optional[str], Advanced()),
        is_null_spec,
        ScalarTester(str, str, 'str'),
    )

    # test handling of nested Unions - note that nested unions are collapsed
    _assert_union_variants(
        getTypeSpec(Union[str, Union[int, None]], Advanced()),
        is_null_spec,
        ScalarTester(str, str, 'str'),
        ScalarTester(int, int, 'int'),
    )

    _assert_union_variants(
        getTypeSpec(Union[str, Optional[int]], Advanced()),
        is_null_spec,
        ScalarTester(str, str, 'str'),
        ScalarTester(int, int, 'int'),
    )

    # test Union containing more complex types: Union[List[], Dict[]]
    _assert_union_variants(
        getTypeSpec(Union[List[int], Dict[str, int]], Advanced()),
        ListTester(ScalarTester(int, int, 'int')),
        DictTester(ScalarTester(int, int, 'int')),
    )

    # test Union containing some NewTypes
    MyInt = NewType('MyInt', int)
    MyStr = NewType('MyStr', str)
    MyInt2 = NewType('MyInt2', MyInt)
    MyStr2 = NewType('MyStr2', MyStr)
    adv = Advanced()
    adv.addNewType(MyInt)
    adv.addNewType(MyStr)
    adv.addNewType(MyInt2)
    adv.addNewType(MyStr2)
    _assert_union_variants(
        getTypeSpec(Union[List[MyInt2], Dict[str, MyStr2]], adv),
        ListTester(ScalarTester(MyInt2, int, 'MyInt2')),
        DictTester(ScalarTester(MyStr2, str, 'MyStr2')),
    )
示例#7
0
def test_Union_type_spec_does_not_collapse_int_bool() -> None:
    """Some versions of python express this bug.

    'Union[int, bool]' becomes just 'int'

    See https://stackoverflow.com/questions/60154326/unable-to-create-unionbool-int-type
    """
    from bifrostrpc.typing import Advanced
    from bifrostrpc.typing import getTypeSpec

    # test handling of a simple Union[int, bool]
    _assert_union_variants(
        getTypeSpec(Union[int, bool], Advanced()),
        ScalarTester(int, int, 'int'),
        ScalarTester(bool, bool, 'bool'),
    )
示例#8
0
def _generateAdvancedTypes(dest: FileTS, adv: Advanced) -> None:
    for name, baseType, children in adv.getNewTypeDetails():
        try:
            tsprimitive = PRIMITIVES[baseType.__name__]
        except KeyError:
            raise Exception(
                f'Cannot generate a typescript alias matching {name}'
                f'; no known primitive type for {baseType.__name__}')

        typeExpr = tsprimitive + ' & {readonly brand?: unique symbol}'
        if children:
            typeExpr = ' | '.join(children) + ' | (' + typeExpr + ')'

        dest.contents.blank()
        dest.contents.also(tsexpr(f'export type {name} = {typeExpr}'))

    for dc in adv.getAllDataclasses():
        dest.contents.blank()
        iface = InterfaceSpec(dc.__name__,
                              tsexport=True,
                              appendto=dest.contents)
        for field in dataclasses.fields(dc):
            generated = _generateCrossType(getTypeSpec(field.type, adv), adv)
            iface.addProperty(field.name, generated)
示例#9
0
文件: python.py 项目: phodge/bifrost
def _getDataclassSpec(
    dc: Type[Any],
    adv: Advanced,
) -> ClassSpec:
    name = dc.__name__
    cls = ClassSpec(name, isdataclass=True)

    for field in dataclasses.fields(dc):
        fieldspec = getTypeSpec(field.type, adv)
        cls.addProperty(field.name, _generateCrossType(fieldspec, adv))

    # the dataclass needs a deserialization method, too
    fromdict = cls.createMethod('fromDict', CrossNewType(name, quoted=True), isstaticmethod=True)
    fromdict.addPositionalArg('data', CrossAny())
    fromdict.addPositionalArg('label', str)

    # constructor part 1 - ensure the provided data is a dict
    with fromdict.withCond(pyexpr('not isinstance(data, dict)')) as cond:
        cond.alsoRaise("TypeError", expr=pyexpr('f"{label} must be a dict"'))

    # constructor part 2 - ensure the __dataclass__ item is present
    with fromdict.withCond(pyexpr(f'data.get("__dataclass__") != {name!r}')) as cond:
        # Tell pylint not to sorry about the use of %-string formatting here -
        # using an f-string to generate an f-string is too error prone:
        # pylint: disable=C0209
        cond.alsoRaise("TypeError",
                       expr=pyexpr('f"{label}[\'__dataclass__\'] must be %s"' % repr(name)))

    names = Names()

    buildargs: List[PanExpr] = []

    # validate each property item
    for field in dataclasses.fields(dc):
        # use a try/catch to assign the dict key to a local variable before we filter/convert it.
        # This allows us to trap the KeyError quickly and explicitly
        fname = field.name
        varname = names.getNewName('', fname, True)
        v_var = PanVar(varname, CrossAny())
        with fromdict.withTryBlock() as tryblock:
            tryblock.alsoDeclare(v_var, None, pyexpr(f'data[{fname!r}]'))
            with tryblock.withCatchBlock('KeyError') as caught:
                # Tell pylint not to sorry about the use of %-string formatting
                # here - using an f-string to generate an f-string is too error
                # prone:
                # pylint: disable=C0209
                caught.alsoRaise("TypeError",
                                 expr=pyexpr('f"{label}[\'%s\'] is missing"' % fname))
        fieldspec = getTypeSpec(field.type, adv)

        # check the local variable's type
        try:
            fromdict.also(_getFilterBlock(varname, f"data[{fname!r}]", fieldspec, names))
        except _FilterNotPossible:
            stmts = Statements()
            copyname = names.getNewName(varname, 'converted', True)
            stmts.also(_getConverterBlock(
                varname,
                copyname,
                f"data[{fname!r}]", fieldspec, names, adv,
            ))
            stmts.alsoAssign(v_var, PanVar(copyname, CrossAny()))
            fromdict.also(stmts)

        buildargs.append(v_var)

    fromdict.alsoReturn(PanCall(name, *buildargs))

    return cls