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
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>>'
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()
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);")
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