def make_example_resolver(): res = Resolver() import metagraph res.register({ "example_plugin": { "abstract_types": {MyAbstractType, MyNumericAbstractType}, "concrete_types": {StrType, IntType, FloatType, OtherType}, "wrappers": {StrNum}, "translators": {int_to_str, str_to_int}, "abstract_algorithms": { abstract_power, abstract_ln, abstract_echo, odict_reverse, }, "concrete_algorithms": { int_power, float_ln, simple_echo, simple_odict_rev, }, } }) return res
def test_unambiguous_subcomponents(): class AbstractType1(AbstractType): properties = { "divisible_by_two": [False, True], "in_fizzbuzz_club": [False, True], } class AbstractType2(AbstractType): properties = { "divisible_by_two": [False, True], } unambiguous_subcomponents = {AbstractType1} res = Resolver() with pytest.raises( KeyError, match="unambiguous subcomponent .* has not been registered"): res.register({ "test_unambiguous_subcomponents": { "abstract_types": {AbstractType2} } }) res = Resolver() with pytest.raises( ValueError, match="unambiguous subcomponent .* has additional properties beyond" ): res.register({ "test_unambiguous_subcomponents": { "abstract_types": {AbstractType1, AbstractType2} } })
def test_union_signatures(): class Abstract1(AbstractType): pass class Abstract2(AbstractType): pass @abstract_algorithm("testing.typing_union_types") def typing_union_types( a: typing.Union[int, float], b: typing.Optional[typing.Union[int, float]], c: typing.Optional[float], d: typing.Union[Abstract1, Abstract2], e: typing.Optional[typing.Union[Abstract1, Abstract2]], f: typing.Optional[Abstract2], ) -> int: pass # pragma: no cover @abstract_algorithm("testing.mg_union_types") def mg_union_types( a: mg.Union[int, float], b: mg.Optional[mg.Union[int, float]], c: mg.Optional[float], d: mg.Union[Abstract1, Abstract2], e: mg.Optional[mg.Union[Abstract1, Abstract2]], f: mg.Optional[Abstract2], ) -> int: pass # pragma: no cover @abstract_algorithm("testing.mg_union_instances") def mg_union_instances( a: mg.Union[Abstract1(), Abstract2()], b: mg.Optional[mg.Union[Abstract1(), Abstract2()]], c: mg.Optional[Abstract2()], ) -> int: pass # pragma: no cover registry = PluginRegistry("test_union_signatures_good") registry.register(Abstract1) registry.register(Abstract2) registry.register(typing_union_types) registry.register(mg_union_types) registry.register(mg_union_instances) res_good = Resolver() res_good.register(registry.plugins) @abstract_algorithm("testing.typing_union_mixed") def typing_union_mixed( a: typing.Union[int, Abstract1]) -> int: # pragma: no cover pass registry = PluginRegistry("test_union_signatures_bad") registry.register(Abstract1) registry.register(typing_union_mixed) with pytest.raises(TypeError, match="Cannot mix"): res_bad = Resolver() res_bad.register(registry.plugins)
def test_list_signatures(): class Abstract1(AbstractType): pass class Abstract2(AbstractType): pass @abstract_algorithm("testing.typing_list_types") def typing_list_types( a: typing.List[int], b: typing.Optional[typing.List[float]], c: typing.Optional[typing.Union[int, float]], d: typing.List[Abstract1], e: typing.Optional[typing.List[Abstract1]], f: typing.Union[int, None], ) -> int: pass # pragma: no cover @abstract_algorithm("testing.mg_list_types") def mg_list_types( a: mg.List[int], b: mg.Optional[mg.List[float]], c: mg.Optional[mg.Union[int, float]], d: mg.List[Abstract1], e: mg.Optional[mg.List[Abstract1]], # This is not currently supported. If the need arises, we should # add mg.UnionList[int, float] which is a Combo with a new kind=UnionList # f: mg.Union[mg.List[int], mg.List[float]], ) -> int: pass # pragma: no cover @abstract_algorithm("testing.mg_list_instances") def mg_list_instances( a: mg.List[Abstract1()], b: mg.Optional[mg.List[Abstract1()]], c: mg.Optional[Abstract2()], ) -> int: pass # pragma: no cover registry = PluginRegistry("test_list_signatures_good") registry.register(Abstract1) registry.register(Abstract2) registry.register(typing_list_types) registry.register(mg_list_types) registry.register(mg_list_instances) res_good = Resolver() res_good.register(registry.plugins) sig = res_good.abstract_algorithms[ "testing.typing_list_types"].__signature__ for letter in "bcef": assert sig.parameters[letter].annotation.optional for letter in "abde": assert sig.parameters[letter].annotation.kind == "List" assert sig.parameters["c"].annotation.kind == "Union" assert sig.parameters["f"].annotation.kind is None
def default_plugin_resolver(request): # pragma: no cover res = Resolver() if request.config.getoption("--no-plugins", default=False): from metagraph.plugins import find_plugins res.register(**find_plugins()) else: res.load_plugins_from_environment() return res
def test_roundtripper(default_plugin_resolver): dpr = default_plugin_resolver # Register translators that aren't round-trippable to induce failures registry = PluginRegistry("test_roundtripper") for at in dpr.abstract_types: registry.register(at) for ct in dpr.concrete_types: registry.register(ct) registry.register( dpr.translators[ (dpr.types.NodeMap.PythonNodeMapType, dpr.types.NodeMap.NumpyNodeMapType) ] ) registry.register( dpr.translators[ (dpr.types.NodeMap.NumpyNodeMapType, dpr.types.NodeMap.GrblasNodeMapType) ] ) registry.register( dpr.translators[ (dpr.types.NodeMap.PythonNodeMapType, dpr.types.NodeSet.PythonNodeSetType) ] ) res = Resolver() res.register(registry.plugins) # If test is in dask mode, convert our resolver to a dask resolver if isinstance(dpr, DaskResolver): res = DaskResolver(res) gnm = dpr.wrappers.NodeMap.GrblasNodeMap( grblas.Vector.from_values([0, 2], [1.1, 5.5]) ) gns = dpr.wrappers.NodeSet.GrblasNodeSet(grblas.Vector.from_values([0, 2], [1, 1])) pnm = {0: 1.1, 2: 5.5} pns = {0, 2} rt = RoundTripper(res) with pytest.raises( UnreachableTranslationError, match="Impossible to return from target" ): rt.verify_round_trip(pnm) with pytest.raises(UnreachableTranslationError, match="Impossible to reach source"): rt.verify_round_trip(gnm) with pytest.raises( TypeError, match="start and end must have different abstract types" ): rt.verify_one_way(pnm, gnm) with pytest.raises(UnreachableTranslationError, match="Impossible to reach source"): rt.verify_one_way(gnm, pns) with pytest.raises( UnreachableTranslationError, match="Impossible to return from target" ): rt.verify_one_way(pnm, gns)
def res(): from metagraph.plugins.core.types import Vector from metagraph.plugins.numpy.types import NumpyVectorType @abstract_algorithm("testing.add") def testing_add(a: Vector, b: Vector) -> Vector: # pragma: no cover pass @concrete_algorithm("testing.add", compiler="numba") def compiled_add( a: NumpyVectorType, b: NumpyVectorType) -> NumpyVectorType: # pragma: no cover return a + b @abstract_algorithm("testing.scale") def testing_scale(a: Vector, scale: float) -> Vector: # pragma: no cover pass @concrete_algorithm("testing.scale", compiler="numba") def compiled_scale(a: NumpyVectorType, scale: float) -> NumpyVectorType: # pragma: no cover return a * scale @abstract_algorithm("testing.offset") def testing_offset(a: Vector, *, offset: float) -> Vector: # pragma: no cover pass @concrete_algorithm("testing.offset", compiler="identity_comp") def compiled_offset(a: NumpyVectorType, *, offset: float) -> NumpyVectorType: # pragma: no cover return a + offset registry = PluginRegistry("test_subgraphs_plugin") registry.register(testing_add) registry.register(compiled_add) registry.register(testing_scale) registry.register(compiled_scale) registry.register(testing_offset) registry.register(compiled_offset) resolver = Resolver() # NumbaCompiler will be picked up from environment resolver.load_plugins_from_environment() resolver.register(registry.plugins) return resolver
def test_unnormalizable(default_plugin_resolver): dpr = default_plugin_resolver # Only register some of the translators to force a multi-step translation path res_small = Resolver() res_small.register({ "foo": { "abstract_types": dpr.abstract_types, "concrete_types": dpr.concrete_types, "wrappers": {PythonNodeMapType, NumpyNodeMap, GrblasNodeMap}, "translators": { dpr.translators[(PythonNodeMapType, NumpyNodeMap.Type)], }, } }) mv = MultiVerify(res_small) mr = MultiResult(mv, {"testing.foo": {0: 1.1, 2: 4.5}}) with pytest.raises(UnsatisfiableAlgorithmError, match="Unable to convert type"): mr.normalize(GrblasNodeMap)
def make_example_resolver(): res = Resolver() import metagraph res.register({ "example_plugin": { "abstract_types": {MyAbstractType, MyNumericAbstractType}, "concrete_types": {StrType, IntType, FloatType, OtherType}, "wrappers": {StrNum, StrNumRot13}, "translators": {int_to_str, str_to_int, str_to_rot13}, "abstract_algorithms": { abstract_power, abstract_ln, abstract_echo, odict_reverse, abstract_repeat, fizzbuzz_club, add_me_up, crazy_inputs, }, "concrete_algorithms": { int_power, float_ln, simple_echo, simple_odict_rev, string_repeat, strnum_fizzbuzz_club, simple_add_me_up, simple_crazy_inputs, }, "compilers": {FailCompiler(), IdentityCompiler()}, }, "example2_plugin": { "concrete_algorithms": {strnum_power} }, "example3_plugin": { "concrete_algorithms": {strnumrot13_power} }, }) return res
def test_invalid_plugin_names(): res = Resolver() invalid_plugin_name = "invalid_name!#@$%#^&*()[]" class Abstract1(AbstractType): pass class Concrete1(ConcreteType, abstract=Abstract1): pass with pytest.raises(ValueError, match="is not a valid plugin name"): registry = PluginRegistry(invalid_plugin_name) registry = PluginRegistry("test_invalid_plugin_names_default_plugin") with pytest.raises(ValueError, match="is not a valid plugin name"): registry.register(Abstract1, invalid_plugin_name) with pytest.raises(ValueError, match="is not a valid plugin name"): registry.register_from_modules(mg.plugins.core.types, mg.plugins.core.algorithms, name=invalid_plugin_name) with pytest.raises(ValueError, match="is not a valid plugin name"): res.register({invalid_plugin_name: {"abstract_types": {Abstract1}}})
def test_register_errors(): res = Resolver() class Abstract1(AbstractType): pass class Concrete1(ConcreteType, abstract=Abstract1): pass registry = PluginRegistry("test_register_errors_default_plugin") registry.register(Concrete1) with pytest.raises(ValueError, match="unregistered abstract"): res.register(registry.plugins) # forgetting to set abstract attribute is tested in test_plugins now class Abstract2(AbstractType): pass class Concrete2(ConcreteType, abstract=Abstract2): pass @translator def c1_to_c2(src: Concrete1, **props) -> Concrete2: # pragma: no cover pass @translator def c2_to_c1(src: Concrete2, **props) -> Concrete1: # pragma: no cover pass registry.register(c1_to_c2) with pytest.raises(ValueError, match="has unregistered abstract type "): res.register(registry.plugins) registry.register(Abstract1) with pytest.raises( ValueError, match="translator destination type .* has not been registered"): res.register(registry.plugins) with pytest.raises( ValueError, match="translator source type .* has not been registered"): res.register({ "test_register_errors_default_plugin": { "translators": {c2_to_c1} } }) # Fresh start res = Resolver() registry.register(Abstract2) registry.register(Concrete2) with pytest.raises(ValueError, match="convert between concrete types"): res.register(registry.plugins) @abstract_algorithm("testing.myalgo") def my_algo(a: Abstract1) -> Abstract2: # pragma: no cover pass @abstract_algorithm("testing.myalgo") def my_algo2(a: Abstract1) -> Abstract2: # pragma: no cover pass my_algo_registry = PluginRegistry("test_register_errors_default_plugin") my_algo_registry.register(my_algo) res.register(my_algo_registry.plugins) with pytest.raises(ValueError, match="already exists"): my_algo2_registry = PluginRegistry( "test_register_errors_default_plugin") my_algo2_registry.register(my_algo2) res.register(my_algo2_registry.plugins) @abstract_algorithm("testing.bad_input_type") def my_algo_bad_list_input_type(a: List) -> int: # pragma: no cover pass @abstract_algorithm("testing.bad_output_type") def my_algo_bad_output_type(a: Abstract1) -> res: # pragma: no cover pass # @abstract_algorithm("testing.bad_compound_output_type") # def my_algo_bad_compound_list_output_type( # a: Abstract1, # ) -> Tuple[List, List]: # pragma: no cover # pass with pytest.raises(TypeError, match="Found an empty list of types for kind=List"): registry = PluginRegistry("test_register_errors_default_plugin") registry.register(my_algo_bad_list_input_type) res_tmp = Resolver() res_tmp.register(registry.plugins) with pytest.raises(TypeError, match="return type may not be an instance of type"): registry = PluginRegistry("test_register_errors_default_plugin") registry.register(my_algo_bad_output_type) res_tmp = Resolver() res_tmp.register(registry.plugins) # with pytest.raises( # TypeError, # match="return type may not be an instance of type <class 'typing.TypeVar'>", # ): # registry = PluginRegistry("test_register_errors_default_plugin") # registry.register(my_algo_bad_compound_list_output_type) # res_tmp = Resolver() # res_tmp.register(registry.plugins) @abstract_algorithm("testing.bad_input_type") def my_algo_bad_dict_input_type(a: Dict) -> Resolver: # pragma: no cover pass @abstract_algorithm("testing.bad_compound_output_type") def my_algo_bad_compound_dict_output_type( a: Abstract1, ) -> Tuple[Dict, Dict]: # pragma: no cover pass with pytest.raises(TypeError, match="may not be typing.Dict"): registry = PluginRegistry("test_register_errors_default_plugin") registry.register(my_algo_bad_dict_input_type) res_tmp = Resolver() res_tmp.register(registry.plugins) with pytest.raises(TypeError, match="may not be typing.Dict"): registry = PluginRegistry("test_register_errors_default_plugin") registry.register(my_algo_bad_compound_dict_output_type) res_tmp = Resolver() res_tmp.register(registry.plugins) @concrete_algorithm("testing.does_not_exist") def my_algo3(a: Abstract1) -> Abstract2: # pragma: no cover pass my_algo3_registry = PluginRegistry("test_register_errors_default_plugin") my_algo3_registry.register(my_algo3) with pytest.raises(ValueError, match="unregistered abstract"): res.register(my_algo3_registry.plugins) class Concrete3(ConcreteType, abstract=Abstract1): value_type = int class Concrete4(ConcreteType, abstract=Abstract1): value_type = int registry = PluginRegistry("test_register_errors_default_plugin") registry.register(Concrete3) registry.register(Concrete4) registry.register(Abstract1) with pytest.raises(ValueError, match=r"abstract type .+ already exists"): res.register(registry.plugins) @concrete_algorithm("testing.myalgo") def conc_algo_with_defaults( a: Concrete1 = 17) -> Concrete2: # pragma: no cover return a with pytest.raises(TypeError, match='argument "a" declares a default value'): registry = PluginRegistry("test_register_errors_default_plugin") registry.register(conc_algo_with_defaults) res.register(registry.plugins) @concrete_algorithm("testing.myalgo", include_resolver=True) def conc_algo_with_resolver_default(a: Concrete1, *, resolver=14) -> Concrete2: return a with pytest.raises(TypeError, match='"resolver" cannot have a default'): registry = PluginRegistry("test_register_errors_default_plugin") registry.register(conc_algo_with_resolver_default) res.register(registry.plugins) @abstract_algorithm("testing.multi_ret") def my_multi_ret_algo() -> Tuple[int, int, int]: # pragma: no cover pass @concrete_algorithm("testing.multi_ret") def conc_algo_wrong_output_nums() -> Tuple[int, int]: # pragma: no cover return (0, 100) with pytest.raises( TypeError, match= "return type is not compatible with abstract function signature", ): registry = PluginRegistry("test_register_errors_default_plugin") registry.register(my_multi_ret_algo) registry.register(conc_algo_wrong_output_nums) res.register(registry.plugins) @abstract_algorithm("testing.any") def abstract_any(x: Any) -> int: # pragma: no cover pass @concrete_algorithm("testing.any") def my_any(x: int) -> int: # pragma: no cover return 12 with pytest.raises( TypeError, match= 'argument "x": .* does not match typing.Any in abstract signature', ): registry = PluginRegistry("test_register_errors_default_plugin") registry.register(abstract_any) registry.register(my_any, "my_any_plugin") res.register(registry.plugins) # Don't allow concrete types in abstract algorithm signature @abstract_algorithm("testing.abst_algo_bad_return_type") def abst_algo_bad_return_type_1(x: int) -> Concrete1: # pragma: no cover pass with pytest.raises(TypeError, match=" may not have Concrete types in signature"): registry = PluginRegistry("test_register_errors_default_plugin") registry.register(abst_algo_bad_return_type_1) res_tmp = Resolver() res_tmp.register(registry.plugins) @abstract_algorithm("testing.abst_algo_bad_parameter_type") def abst_algo_bad_parameter_type_1( x: Concrete1) -> int: # pragma: no cover pass with pytest.raises(TypeError, match=" may not have Concrete types in signature"): registry = PluginRegistry("test_register_errors_default_plugin") registry.register(abst_algo_bad_parameter_type_1) res_tmp = Resolver() res_tmp.register(registry.plugins) @abstract_algorithm("testing.abst_algo_bad_return_type") def abst_algo_bad_return_type_2( x: int) -> List[Concrete1]: # pragma: no cover pass with pytest.raises( TypeError, match="return type may not have Concrete types in signature"): registry = PluginRegistry("test_register_errors_default_plugin") registry.register(abst_algo_bad_return_type_2) res_tmp = Resolver() res_tmp.register(registry.plugins) @abstract_algorithm("testing.abst_algo_bad_parameter_type") def abst_algo_bad_parameter_type_2( x: List[Concrete1]) -> int: # pragma: no cover pass with pytest.raises( TypeError, match='argument "x" may not have Concrete types in signature'): registry = PluginRegistry("test_register_errors_default_plugin") registry.register(abst_algo_bad_parameter_type_2) res_tmp = Resolver() res_tmp.register(registry.plugins) @abstract_algorithm("testing.abst_algo_combo_combo_bad") def abst_algo_combo_combo_bad(x: Union[List[int], List[float]]) -> int: pass with pytest.raises( TypeError, match="Nesting a Combo type inside a Combo type is not allowed"): registry = PluginRegistry("test_register_errors_combo_combo") registry.register(abst_algo_combo_combo_bad) res_tmp = Resolver() res_tmp.register(registry.plugins) @abstract_algorithm("testing.abst_algo_combo_combo_good") def abst_algo_combo_combo_good(x: Optional[List[int]]) -> int: pass @concrete_algorithm("testing.abst_algo_combo_combo_good") def my_algo_combo_combo_good(x: Optional[List[int]]) -> int: return 12 registry = PluginRegistry("test_register_good_combo_combo") registry.register(abst_algo_combo_combo_good) registry.register(my_algo_combo_combo_good) res_tmp = Resolver() res_tmp.register(registry.plugins) @concrete_algorithm("testing.abst_algo_combo_combo_good") def my_algo_combo_combo_not_so_good( x: Union[List[int], List[float]]) -> int: return 12 with pytest.raises( TypeError, match="Nesting a Combo type inside a Combo type is not allowed"): registry = PluginRegistry("test_register_good_combo_combo") registry.register(abst_algo_combo_combo_good) registry.register(my_algo_combo_combo_not_so_good) res_tmp = Resolver() res_tmp.register(registry.plugins) # Return type cannot be a Combo @abstract_algorithm("testing.combos") def abst_combos(x: Union[int, float]) -> int: pass @concrete_algorithm("testing.combos") def conc_combos(x: Union[int, float]) -> Union[int, float]: return x with pytest.raises(TypeError, match="return type may not be a Combo"): registry = PluginRegistry("test_register_combos") registry.register(abst_combos) registry.register(conc_combos) res_tmp = Resolver() res_tmp.register(registry.plugins) # Optional flag of a Combo must match what the abstract definition @concrete_algorithm("testing.combos") def conc_combos2(x: Optional[Union[int, float]]) -> int: return x with pytest.raises(TypeError, match="does not match optional flag in"): registry = PluginRegistry("test_register_combos") registry.register(abst_combos) registry.register(conc_combos2) res_tmp = Resolver() res_tmp.register(registry.plugins) # Subtypes of a Combo must match those in the abstract definition @concrete_algorithm("testing.combos") def conc_combos3(x: Union[complex, str]) -> int: return x with pytest.raises(TypeError, match="not found in mg.Union"): registry = PluginRegistry("test_register_combos") registry.register(abst_combos) registry.register(conc_combos3) res_tmp = Resolver() res_tmp.register(registry.plugins)
def test_algorithm_versions(): @abstract_algorithm("test_algorithm_versions.test_abstract_algo") def abstract_algo1(input_int: int): pass # pragma: no cover @abstract_algorithm("test_algorithm_versions.test_abstract_algo", version=1) def abstract_algo2(input_int: int): pass # pragma: no cover @concrete_algorithm("test_algorithm_versions.test_abstract_algo") def concrete_algo1(input_int: int): pass # pragma: no cover @concrete_algorithm("test_algorithm_versions.test_abstract_algo", version=1) def concrete_algo2(input_int: int): pass # pragma: no cover # Sanity check res = Resolver() res.register({ "test_algorithm_versions1": { "abstract_algorithms": {abstract_algo1, abstract_algo2}, "concrete_algorithms": {concrete_algo1, concrete_algo2}, } }) assert (res.algos.test_algorithm_versions.test_abstract_algo. test_algorithm_versions1._algo.__name__ == "concrete_algo2") # Unknown concrete, raise with pytest.raises(ValueError, match="implements an unknown version"): with config.set({"core.algorithm.unknown_concrete_version": "raise"}): res = Resolver() res.register({ "test_algorithm_versions1": { "abstract_algorithms": {abstract_algo1}, "concrete_algorithms": {concrete_algo1, concrete_algo2}, } }) # Unknown concrete, use version 0 with config.set({"core.algorithm.unknown_concrete_version": "ignore"}): res = Resolver() res.register({ "test_algorithm_versions1": { "abstract_algorithms": {abstract_algo1}, "concrete_algorithms": {concrete_algo1, concrete_algo2}, } }) assert (res.algos.test_algorithm_versions.test_abstract_algo. test_algorithm_versions1._algo.__name__ == "concrete_algo1") # Outdated concrete, raise with pytest.raises( ValueError, match="implements an outdated version of abstract algorithm"): with config.set({"core.algorithms.outdated_concrete_version": "raise"}): res = Resolver() res.register({ "test_algorithm_versions1": { "abstract_algorithms": {abstract_algo1, abstract_algo2}, "concrete_algorithms": {concrete_algo1}, } }) # Outdated concrete, ignore b/c/ not the latest with config.set({"core.algorithms.outdated_concrete_version": None}): res = Resolver() res.register({ "test_algorithm_versions1": { "abstract_algorithms": {abstract_algo1, abstract_algo2}, "concrete_algorithms": {concrete_algo1}, } }) assert not hasattr(res.algos.test_algorithm_versions.test_abstract_algo, "test_algorithm_versions1") # Outdated concrete, warn with pytest.warns( AlgorithmWarning, match="implements an outdated version of abstract algorithm"): with config.set({"core.algorithms.outdated_concrete_version": "warn"}): res = Resolver() res.register({ "test_algorithm_versions1": { "abstract_algorithms": {abstract_algo1, abstract_algo2}, "concrete_algorithms": {concrete_algo1}, } })
def test_duplicate_plugin(): class AbstractType1(AbstractType): pass class AbstractType2(AbstractType): pass class ConcreteType1(ConcreteType, abstract=AbstractType1): value_type = int pass class ConcreteType2(ConcreteType, abstract=AbstractType2): value_type = int pass @abstract_algorithm("test_duplicate_plugin.test_abstract_algo") def abstract_algo1(input_int: int): pass # pragma: no cover @concrete_algorithm("test_duplicate_plugin.test_abstract_algo") def concrete_algo1(input_int: int): pass # pragma: no cover @concrete_algorithm("test_duplicate_plugin.test_abstract_algo") def concrete_algo2(input_int: int): pass # pragma: no cover res = Resolver() with pytest.raises( ValueError, match="Multiple concrete algorithms for abstract algorithm"): res.register({ "bad_many_conc_algo_to_one_abstract_algo": { "abstract_algorithms": {abstract_algo1}, "concrete_algorithms": {concrete_algo1, concrete_algo2}, } }) res = Resolver() res.register(({ "bad_many_value_type_to_concrete": { "abstract_types": {AbstractType1, AbstractType2}, "concrete_types": {ConcreteType1}, } })) with pytest.raises( ValueError, match= "Python class '<class 'int'>' already has a registered concrete type: ", ): res.register(({ "bad_many_value_type_to_concrete": { "concrete_types": {ConcreteType2} } })) res = Resolver() res.register( {"test_duplicate_plugin": { "abstract_types": {AbstractType1} }}) with pytest.raises(ValueError, match=" already registered."): res.register( {"test_duplicate_plugin": { "concrete_types": {ConcreteType1} }}) with pytest.raises(ValueError, match=" not known to be the resolver or a plugin."): _ResolverRegistrar.register_plugin_attributes_in_tree( Resolver(), Resolver(), abstract_types={AbstractType1})
def test_register_errors(): res = Resolver() class Abstract1(AbstractType): pass class Concrete1(ConcreteType, abstract=Abstract1): pass registry = PluginRegistry("test_register_errors_default_plugin") registry.register(Concrete1) with pytest.raises(ValueError, match="unregistered abstract"): res.register(registry.plugins) # forgetting to set abstract attribute is tested in test_plugins now class Abstract2(AbstractType): pass class Concrete2(ConcreteType, abstract=Abstract2): pass @translator def c1_to_c2(src: Concrete1, **props) -> Concrete2: # pragma: no cover pass registry.register(c1_to_c2) with pytest.raises(ValueError, match="has unregistered abstract type "): res.register(registry.plugins) registry.register(Abstract1) with pytest.raises( ValueError, match="translator destination type .* has not been registered" ): res.register(registry.plugins) # Fresh start -- too much baggage of things partially registered above res = Resolver() registry.register(Abstract2) registry.register(Concrete2) with pytest.raises(ValueError, match="convert between concrete types"): res.register(registry.plugins) @abstract_algorithm("testing.myalgo") def my_algo(a: Abstract1) -> Abstract2: # pragma: no cover pass @abstract_algorithm("testing.myalgo") def my_algo2(a: Abstract1) -> Abstract2: # pragma: no cover pass my_algo_registry = PluginRegistry("test_register_errors_default_plugin") my_algo_registry.register(my_algo) res.register(my_algo_registry.plugins) with pytest.raises(ValueError, match="already exists"): my_algo2_registry = PluginRegistry("test_register_errors_default_plugin") my_algo2_registry.register(my_algo2) res.register(my_algo2_registry.plugins) @abstract_algorithm("testing.bad_input_type") def my_algo_bad_input_type(a: List) -> Resolver: # pragma: no cover pass @abstract_algorithm("testing.bad_output_type") def my_algo_bad_output_type(a: Abstract1) -> res: # pragma: no cover pass @abstract_algorithm("testing.bad_compound_output_type") def my_algo_bad_compound_output_type( a: Abstract1, ) -> Tuple[List, List]: # pragma: no cover pass with pytest.raises(TypeError, match='argument "a" may not be typing.List'): registry = PluginRegistry("test_register_errors_default_plugin") registry.register(my_algo_bad_input_type) res_tmp = Resolver() res_tmp.register(registry.plugins) with pytest.raises(TypeError, match="return type may not be an instance of"): registry = PluginRegistry("test_register_errors_default_plugin") registry.register(my_algo_bad_output_type) res_tmp = Resolver() res_tmp.register(registry.plugins) with pytest.raises(TypeError, match="return type may not be typing.List"): registry = PluginRegistry("test_register_errors_default_plugin") registry.register(my_algo_bad_compound_output_type) res_tmp = Resolver() res_tmp.register(registry.plugins) @concrete_algorithm("testing.does_not_exist") def my_algo3(a: Abstract1) -> Abstract2: # pragma: no cover pass my_algo3_registry = PluginRegistry("test_register_errors_default_plugin") my_algo3_registry.register(my_algo3) with pytest.raises(ValueError, match="unregistered abstract"): res.register(my_algo3_registry.plugins) class Concrete3(ConcreteType, abstract=Abstract1): value_type = int class Concrete4(ConcreteType, abstract=Abstract1): value_type = int registry = PluginRegistry("test_register_errors_default_plugin") registry.register(Concrete3) registry.register(Concrete4) registry.register(Abstract1) with pytest.raises(ValueError, match=r"abstract type .+ already exists"): res.register(registry.plugins) @concrete_algorithm("testing.myalgo") def conc_algo_with_defaults(a: Concrete1 = 17) -> Concrete2: # pragma: no cover return a with pytest.raises(TypeError, match='argument "a" declares a default value'): registry = PluginRegistry("test_register_errors_default_plugin") registry.register(conc_algo_with_defaults) res.register(registry.plugins) @abstract_algorithm("testing.multi_ret") def my_multi_ret_algo() -> Tuple[int, int, int]: # pragma: no cover pass @concrete_algorithm("testing.multi_ret") def conc_algo_wrong_output_nums() -> Tuple[int, int]: # pragma: no cover return (0, 100) with pytest.raises( TypeError, match="return type is not compatible with abstract function signature", ): registry = PluginRegistry("test_register_errors_default_plugin") registry.register(my_multi_ret_algo) registry.register(conc_algo_wrong_output_nums) res.register(registry.plugins) @abstract_algorithm("testing.any") def abstract_any(x: Any) -> int: # pragma: no cover pass @concrete_algorithm("testing.any") def my_any(x: int) -> int: # pragma: no cover return 12 with pytest.raises( TypeError, match='argument "x" does not match abstract function signature' ): registry = PluginRegistry("test_register_errors_default_plugin") registry.register(abstract_any) registry.register(my_any, "my_any_plugin") res.register(registry.plugins)