diff --git a/CHANGELOG.md b/CHANGELOG.md index 55cb792..e52e39d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ Opyoid follows [semver guidelines](https://semver.org) for versioning. ## Unreleased +## 2.1.0 +### Features +- Added `__opyoid_post_init__` method support to allow breaking dependency loops, check the README for more details + ## 2.0.0 ### Breaking changes - Remove support for Python < 3.8 diff --git a/opyoid/bindings/abstract_module.py b/opyoid/bindings/abstract_module.py index 42b4def..072ca04 100644 --- a/opyoid/bindings/abstract_module.py +++ b/opyoid/bindings/abstract_module.py @@ -63,7 +63,7 @@ def install(self, module: Union["AbstractModule", Type["AbstractModule"]]) -> No continue if isinstance(binding, RegisteredMultiBinding): binding = RegisteredMultiBinding( - cast(Binding[List[Any]], binding.raw_binding), + binding.raw_binding, item_bindings=[ RegisteredBinding( registered_item_binding.raw_binding, diff --git a/opyoid/bindings/self_binding/callable_to_provider_adapter.py b/opyoid/bindings/self_binding/callable_to_provider_adapter.py index 4e27f53..682fa73 100644 --- a/opyoid/bindings/self_binding/callable_to_provider_adapter.py +++ b/opyoid/bindings/self_binding/callable_to_provider_adapter.py @@ -1,15 +1,13 @@ import logging -from inspect import Parameter, signature -from typing import Any, Callable, Dict, List, Optional, Type +from typing import Callable, Optional, Type -from opyoid.bindings.instance_binding import FromInstanceProvider from opyoid.exceptions import NoBindingFound, NonInjectableTypeError from opyoid.injection_context import InjectionContext from opyoid.provider import Provider from opyoid.target import Target -from opyoid.type_checker import TypeChecker -from opyoid.utils import EMPTY, get_class_full_name, InjectedT +from opyoid.utils import InjectedT from .from_callable_provider import FromCallableProvider +from .parameters_provider import ParametersProvider from ...scopes import Scope @@ -18,6 +16,9 @@ class CallableToProviderAdapter: logger = logging.getLogger(__name__) + def __init__(self): + self._parameters_provider = ParametersProvider() + def create( self, type_or_function: Callable[..., InjectedT], @@ -29,35 +30,16 @@ def create( ) if cached_provider: return cached_provider - if isinstance(type_or_function, type): - parameters = list(signature(type_or_function.__init__).parameters.values())[1:] - else: - parameters = signature(type_or_function).parameters.values() - positional_providers: List[Provider[Any]] = [] - args_provider: Optional[Provider[List[Any]]] = None - keyword_providers: Dict[str, Provider[Any]] = {} - for parameter in parameters: - context.current_parameter = parameter - # Ignore '**kwargs' - if parameter.kind == Parameter.VAR_KEYWORD: - continue - if parameter.kind == Parameter.VAR_POSITIONAL: - # *args - args_provider = self._get_positional_parameter_provider(parameter, type_or_function, context) - continue - parameter_provider = self._get_parameter_provider(parameter, type_or_function, context) - if parameter.kind == Parameter.KEYWORD_ONLY: - # After *args - keyword_providers[parameter.name] = parameter_provider - else: - # Before *args - positional_providers.append(parameter_provider) + positional_providers, args_provider, keyword_providers = self._parameters_provider.get_parameters_provider( + type_or_function, context + ) unscoped_provider = FromCallableProvider( type_or_function, positional_providers, args_provider, keyword_providers, + context, ) scope_context: InjectionContext[Scope] = context.get_child_context(Target(scope)) try: @@ -69,75 +51,3 @@ def create( provider = scope_provider.get().get_scoped_provider(unscoped_provider) context.injection_state.provider_registry.set_provider(context.target, provider) return provider - - def _get_parameter_provider( - self, parameter: Parameter, type_or_function: Callable[..., InjectedT], context: InjectionContext[InjectedT] - ) -> Provider[InjectedT]: - default_value = parameter.default if parameter.default is not Parameter.empty else EMPTY - if parameter.annotation is not Parameter.empty: - if TypeChecker.is_named(parameter.annotation): - provider: Optional[Provider[InjectedT]] = self._get_provider( - [Target(parameter.annotation.original_type, parameter.annotation.name, default_value)], context - ) - else: - provider = self._get_provider( - [ - Target(parameter.annotation, parameter.name, default_value), - Target(parameter.annotation, None, default_value), - ], - context, - ) - if provider: - return provider - if parameter.default is not Parameter.empty: - return FromInstanceProvider(parameter.default) - raise NonInjectableTypeError( - f"Could not find a binding or a default value for {parameter.name}: " - f"{get_class_full_name(parameter.annotation)} required by {type_or_function}" - ) - - def _get_positional_parameter_provider( - self, parameter: Parameter, type_or_function: Callable[..., InjectedT], context: InjectionContext[InjectedT] - ) -> Provider[List[InjectedT]]: - if parameter.annotation is Parameter.empty: - return FromInstanceProvider([]) - if TypeChecker.is_named(parameter.annotation): - provider: Optional[Provider[List[InjectedT]]] = self._get_provider( - [ - Target( - List[parameter.annotation.original_type], # type: ignore[name-defined] - parameter.annotation.name, - default=[], - ) - ], - context, - ) - else: - provider = self._get_provider( - [ - Target(List[parameter.annotation], parameter.name, default=[]), # type: ignore[name-defined] - Target(List[parameter.annotation], default=[]), # type: ignore[name-defined] - ], - context, - ) - if provider: - return provider - self.logger.debug( - f"Could not find a binding for *{parameter.name}: {parameter.annotation} required by " - f"{type_or_function}, will inject nothing" - ) - return FromInstanceProvider([]) - - @staticmethod - def _get_provider( - targets: List[Target[InjectedT]], parent_context: InjectionContext[Any] - ) -> Optional[Provider[InjectedT]]: - for target_index, target in enumerate(targets): - context = parent_context.get_child_context(target, allow_jit_provider=target_index == len(targets) - 1) - context.current_class = parent_context.current_class - context.current_parameter = parent_context.current_parameter - try: - return context.get_provider() - except NoBindingFound: - pass - return None diff --git a/opyoid/bindings/self_binding/from_callable_provider.py b/opyoid/bindings/self_binding/from_callable_provider.py index 5b74e09..3ecbaad 100644 --- a/opyoid/bindings/self_binding/from_callable_provider.py +++ b/opyoid/bindings/self_binding/from_callable_provider.py @@ -1,5 +1,8 @@ from typing import Any, Callable, Dict, List, Optional +from opyoid.bindings.self_binding.parameters_provider import ParametersProvider +from opyoid.constants import OPYOID_POST_INIT +from opyoid.injection_context import InjectionContext from opyoid.provider import Provider from opyoid.utils import InjectedT @@ -11,18 +14,42 @@ def __init__( positional_providers: List[Provider[Any]], args_provider: Optional[Provider[List[Any]]], keyword_providers: Dict[str, Provider[Any]], + injection_context: InjectionContext, ) -> None: self._injected_callable = injected_callable self._positional_providers = positional_providers self._args_provider = args_provider self._keyword_providers = keyword_providers + self._injection_context = injection_context + self._parameters_provider = ParametersProvider() def get(self) -> InjectedT: args = [positional_provider.get() for positional_provider in self._positional_providers] if self._args_provider: args += self._args_provider.get() kwargs = {arg_name: keyword_provider.get() for arg_name, keyword_provider in self._keyword_providers.items()} - return self._injected_callable( + result = self._injected_callable( *args, **kwargs, ) + if hasattr(self._injected_callable, OPYOID_POST_INIT): + self._injection_context.injection_state.add_post_init_callback(lambda: self._run_post_init(result)) + return result + + def _run_post_init(self, instance: InjectedT) -> None: + ( + post_init_positional_providers, + post_init_args_provider, + post_init_keyword_providers, + ) = self._parameters_provider.get_parameters_provider( + getattr(instance, OPYOID_POST_INIT), + self._injection_context, + ) + + post_init_args = [positional_provider.get() for positional_provider in post_init_positional_providers] + if post_init_args_provider: + post_init_args += post_init_args_provider.get() + post_init_kwargs = { + arg_name: keyword_provider.get() for arg_name, keyword_provider in post_init_keyword_providers.items() + } + getattr(instance, OPYOID_POST_INIT)(*post_init_args, **post_init_kwargs) diff --git a/opyoid/bindings/self_binding/parameters_provider.py b/opyoid/bindings/self_binding/parameters_provider.py new file mode 100644 index 0000000..55c8d1d --- /dev/null +++ b/opyoid/bindings/self_binding/parameters_provider.py @@ -0,0 +1,123 @@ +import inspect +import logging +import sys +from inspect import Parameter, signature +from typing import Any, Callable, Dict, List, Optional, Tuple + +from opyoid.bindings.instance_binding import FromInstanceProvider +from opyoid.exceptions import NoBindingFound, NonInjectableTypeError +from opyoid.injection_context import InjectionContext +from opyoid.provider import Provider +from opyoid.target import Target +from opyoid.type_checker import TypeChecker +from opyoid.utils import EMPTY, get_class_full_name, InjectedT + + +class ParametersProvider: + """Creates Providers from a callable.""" + + logger = logging.getLogger(__name__) + + def get_parameters_provider( + self, + type_or_function: Callable[..., InjectedT], + context: InjectionContext[InjectedT], + ) -> Tuple[List[Provider[Any]], Optional[Provider[List[Any]]], Dict[str, Provider[Any]]]: + if sys.version_info[:2] < (3, 9) and isinstance(type_or_function, type): + parameters = list(signature(type_or_function.__init__).parameters.values())[1:] + else: + parameters = list(signature(type_or_function).parameters.values()) + + positional_providers: List[Provider[Any]] = [] + args_provider: Optional[Provider[List[Any]]] = None + keyword_providers: Dict[str, Provider[Any]] = {} + for parameter in parameters: + context.current_parameter = parameter + # Ignore '**kwargs' + if parameter.kind == Parameter.VAR_KEYWORD: + continue + + if parameter.kind == Parameter.VAR_POSITIONAL: + # *args + args_provider = self._get_positional_parameter_provider(parameter, type_or_function, context) + continue + parameter_provider = self._get_parameter_provider(parameter, type_or_function, context) + if parameter.kind == Parameter.KEYWORD_ONLY: + # After *args + keyword_providers[parameter.name] = parameter_provider + else: + # Before *args + positional_providers.append(parameter_provider) + return positional_providers, args_provider, keyword_providers + + def _get_parameter_provider( + self, parameter: Parameter, type_or_function: Callable[..., InjectedT], context: InjectionContext[InjectedT] + ) -> Provider[InjectedT]: + default_value = parameter.default if parameter.default is not Parameter.empty else EMPTY + if parameter.annotation is not Parameter.empty: + if TypeChecker.is_named(parameter.annotation): + provider: Optional[Provider[InjectedT]] = self._get_provider( + [Target(parameter.annotation.original_type, parameter.annotation.name, default_value)], context + ) + else: + provider = self._get_provider( + [ + Target(parameter.annotation, parameter.name, default_value), + Target(parameter.annotation, None, default_value), + ], + context, + ) + if provider: + return provider + if parameter.default is not Parameter.empty: + return FromInstanceProvider(parameter.default) + raise NonInjectableTypeError( + f"Could not find a binding or a default value for {parameter.name}: " + f"{get_class_full_name(parameter.annotation)} required by {type_or_function}" + ) + + def _get_positional_parameter_provider( + self, parameter: Parameter, type_or_function: Callable[..., InjectedT], context: InjectionContext[InjectedT] + ) -> Provider[List[InjectedT]]: + if parameter.annotation is Parameter.empty: + return FromInstanceProvider([]) + if TypeChecker.is_named(parameter.annotation): + provider: Optional[Provider[List[InjectedT]]] = self._get_provider( + [ + Target( + List[parameter.annotation.original_type], # type: ignore[name-defined] + parameter.annotation.name, + default=[], + ) + ], + context, + ) + else: + provider = self._get_provider( + [ + Target(List[parameter.annotation], parameter.name, default=[]), # type: ignore[name-defined] + Target(List[parameter.annotation], default=[]), # type: ignore[name-defined] + ], + context, + ) + if provider: + return provider + self.logger.debug( + f"Could not find a binding for *{parameter.name}: {parameter.annotation} required by " + f"{type_or_function}, will inject nothing" + ) + return FromInstanceProvider([]) + + @staticmethod + def _get_provider( + targets: List[Target[InjectedT]], parent_context: InjectionContext[Any] + ) -> Optional[Provider[InjectedT]]: + for target_index, target in enumerate(targets): + context = parent_context.get_child_context(target, allow_jit_provider=target_index == len(targets) - 1) + context.current_class = parent_context.current_class + context.current_parameter = parent_context.current_parameter + try: + return context.get_provider() + except NoBindingFound: + pass + return None diff --git a/opyoid/constants.py b/opyoid/constants.py new file mode 100644 index 0000000..573a8c5 --- /dev/null +++ b/opyoid/constants.py @@ -0,0 +1 @@ +OPYOID_POST_INIT = "__opyoid_post_init__" diff --git a/opyoid/injection_context.py b/opyoid/injection_context.py index 739abb5..403ef1f 100644 --- a/opyoid/injection_context.py +++ b/opyoid/injection_context.py @@ -1,6 +1,6 @@ import logging from inspect import Parameter -from typing import Any, Generic, List, Optional, Type, TYPE_CHECKING, TypeVar +from typing import Any, Callable, Generic, List, Optional, Type, TYPE_CHECKING, TypeVar import attr diff --git a/opyoid/injection_state.py b/opyoid/injection_state.py index d3c1196..f8626a5 100644 --- a/opyoid/injection_state.py +++ b/opyoid/injection_state.py @@ -1,4 +1,4 @@ -from typing import Dict, Optional, TYPE_CHECKING +from typing import Callable, Dict, List, Optional, TYPE_CHECKING import attr @@ -18,3 +18,10 @@ class InjectionState: parent_state: Optional["InjectionState"] = None provider_registry: ProviderRegistry = attr.Factory(ProviderRegistry) state_by_module: Dict[AbstractModule, "InjectionState"] = attr.Factory(dict) + post_init_callbacks: List[Callable[[], None]] = attr.Factory(list) + + def add_post_init_callback(self, callback: Callable[[], None]) -> None: + if self.parent_state: + self.parent_state.add_post_init_callback(callback) + else: + self.post_init_callbacks.append(callback) diff --git a/opyoid/injector.py b/opyoid/injector.py index cc31f9b..2277bbd 100644 --- a/opyoid/injector.py +++ b/opyoid/injector.py @@ -3,6 +3,7 @@ from .bindings import Binding from .bindings.abstract_module import AbstractModule from .bindings.root_module import RootModule +from .constants import OPYOID_POST_INIT from .injection_context import InjectionContext from .injection_state import InjectionState from .injector_options import InjectorOptions @@ -40,4 +41,7 @@ def __init__( def inject(self, target_type: Union[Type[InjectedT], TypeVar, Any], *, named: Optional[str] = None) -> InjectedT: injection_context: InjectionContext[InjectedT] = InjectionContext(Target(target_type, named), self._root_state) - return injection_context.get_provider().get() + result = injection_context.get_provider().get() + for post_init_callback in self._root_state.post_init_callbacks: + post_init_callback() + return result diff --git a/opyoid/providers/providers_factories/set_provider_factory.py b/opyoid/providers/providers_factories/set_provider_factory.py index 6b6d0b8..ae3173b 100644 --- a/opyoid/providers/providers_factories/set_provider_factory.py +++ b/opyoid/providers/providers_factories/set_provider_factory.py @@ -20,5 +20,7 @@ def create(self, context: InjectionContext[InjectedT]) -> Provider[InjectedT]: context.target.named, ) new_context = context.get_child_context(new_target) - return FromCallableProvider(cast(Callable[..., InjectedT], set), [new_context.get_provider()], None, {}) + return FromCallableProvider( + cast(Callable[..., InjectedT], set), [new_context.get_provider()], None, {}, context + ) raise IncompatibleProviderFactory diff --git a/opyoid/providers/providers_factories/tuple_provider_factory.py b/opyoid/providers/providers_factories/tuple_provider_factory.py index c70c3d2..320a2c9 100644 --- a/opyoid/providers/providers_factories/tuple_provider_factory.py +++ b/opyoid/providers/providers_factories/tuple_provider_factory.py @@ -20,5 +20,7 @@ def create(self, context: InjectionContext[InjectedT]) -> Provider[InjectedT]: context.target.named, ) new_context = context.get_child_context(new_target) - return FromCallableProvider(cast(Callable[..., InjectedT], tuple), [new_context.get_provider()], None, {}) + return FromCallableProvider( + cast(Callable[..., InjectedT], tuple), [new_context.get_provider()], None, {}, context + ) raise IncompatibleProviderFactory diff --git a/tests/test_bindings/test_self_binding/test_from_callable_provider.py b/tests/test_bindings/test_self_binding/test_from_callable_provider.py index 33ac2f4..c657c06 100644 --- a/tests/test_bindings/test_self_binding/test_from_callable_provider.py +++ b/tests/test_bindings/test_self_binding/test_from_callable_provider.py @@ -2,16 +2,20 @@ from unittest.mock import create_autospec from opyoid.bindings import FromCallableProvider +from opyoid.injection_context import InjectionContext from opyoid.provider import Provider class TestFromClassProvider(unittest.TestCase): + def setUp(self) -> None: + self.context = create_autospec(InjectionContext, spec_set=True) + def test_provider_without_args(self): class MyType: def __init__(self): pass - provider = FromCallableProvider(MyType, [], None, {}) + provider = FromCallableProvider(MyType, [], None, {}, self.context) instance = provider.get() self.assertIsInstance(instance, MyType) @@ -31,9 +35,12 @@ def __init__(self, *args, **kwargs): provider_4.get.return_value = "value_4" provider_5 = create_autospec(Provider) provider_5.get.return_value = "value_5" - provider = FromCallableProvider( - MyType, [provider_1, provider_2], provider_3, {"kwarg_1": provider_4, "kwarg_2": provider_5} + MyType, + [provider_1, provider_2], + provider_3, + {"kwarg_1": provider_4, "kwarg_2": provider_5}, + self.context, ) instance = provider.get() self.assertIsInstance(instance, MyType) diff --git a/tests/test_named.py b/tests/test_named.py index 1e41078..9a0a48c 100644 --- a/tests/test_named.py +++ b/tests/test_named.py @@ -24,7 +24,7 @@ def __init__(self, my_param: MyType, my_other_param: MyOtherType): self.my_param = my_param self.my_other_param = my_other_param - parameters = signature(MyClass.__init__).parameters + parameters = signature(MyClass).parameters my_param = parameters["my_param"] self.assertTrue(issubclass(my_param.annotation, Named)) self.assertEqual(MyType, my_param.annotation.original_type) @@ -37,7 +37,7 @@ class MyClass: def __init__(self, my_param: int = 3): self.my_param = my_param - parameters = signature(MyClass.__init__).parameters + parameters = signature(MyClass).parameters my_param = parameters["my_param"] self.assertTrue(issubclass(my_param.annotation, Named)) self.assertEqual(int, my_param.annotation.original_type) diff --git a/tests/test_scopes/test_immediate_scope.py b/tests/test_scopes/test_immediate_scope.py index 0b12eeb..7db1a10 100644 --- a/tests/test_scopes/test_immediate_scope.py +++ b/tests/test_scopes/test_immediate_scope.py @@ -1,7 +1,9 @@ import unittest +from unittest.mock import create_autospec from opyoid import ImmediateScope from opyoid.bindings import FromCallableProvider +from opyoid.injection_context import InjectionContext from opyoid.scopes.singleton_scoped_provider import SingletonScopedProvider @@ -12,7 +14,8 @@ class MyType: class TestImmediateScope(unittest.TestCase): def setUp(self) -> None: self.scope = ImmediateScope() - self.class_provider = FromCallableProvider(MyType, [], None, {}) + self.context = create_autospec(InjectionContext, spec_set=True) + self.class_provider = FromCallableProvider(MyType, [], None, {}, self.context) def test_get_scoped_provider_returns_singleton_scoped_provider(self): scoped_provider = self.scope.get_scoped_provider(self.class_provider) @@ -28,6 +31,6 @@ class MyOtherType: def __init__(self): MyOtherType.created_count += 1 - class_provider = FromCallableProvider(MyOtherType, [], None, {}) + class_provider = FromCallableProvider(MyOtherType, [], None, {}, self.context) self.scope.get_scoped_provider(class_provider) self.assertEqual(1, MyOtherType.created_count) diff --git a/tests/test_scopes/test_per_lookup_scope.py b/tests/test_scopes/test_per_lookup_scope.py index 14b1f8b..004f780 100644 --- a/tests/test_scopes/test_per_lookup_scope.py +++ b/tests/test_scopes/test_per_lookup_scope.py @@ -1,7 +1,9 @@ import unittest +from unittest.mock import create_autospec from opyoid import PerLookupScope from opyoid.bindings import FromCallableProvider +from opyoid.injection_context import InjectionContext class MyType: @@ -11,7 +13,8 @@ class MyType: class TestPerLookupScope(unittest.TestCase): def setUp(self) -> None: self.scope = PerLookupScope() - self.class_provider = FromCallableProvider(MyType, [], None, {}) + self.context = create_autospec(InjectionContext, spec_set=True) + self.class_provider = FromCallableProvider(MyType, [], None, {}, self.context) def test_get_scoped_provider_returns_unscoped_provider(self): scoped_provider = self.scope.get_scoped_provider(self.class_provider) diff --git a/tests/test_scopes/test_singleton_scoped_provider.py b/tests/test_scopes/test_singleton_scoped_provider.py index 98dca29..3dc4384 100644 --- a/tests/test_scopes/test_singleton_scoped_provider.py +++ b/tests/test_scopes/test_singleton_scoped_provider.py @@ -1,8 +1,10 @@ import unittest from queue import Queue from threading import Thread +from unittest.mock import create_autospec from opyoid.bindings import FromCallableProvider +from opyoid.injection_context import InjectionContext from opyoid.scopes.singleton_scoped_provider import SingletonScopedProvider @@ -12,7 +14,8 @@ class MyType: class TestSingletonScopedProvider(unittest.TestCase): def setUp(self) -> None: - self.class_provider = FromCallableProvider(MyType, [], None, {}) + self.context = create_autospec(InjectionContext, spec_set=True) + self.class_provider = FromCallableProvider(MyType, [], None, {}, self.context) self.provider = SingletonScopedProvider(self.class_provider) def test_get_returns_instance(self): diff --git a/tests/test_scopes/test_thread_scoped_provider.py b/tests/test_scopes/test_thread_scoped_provider.py index 0337ad2..fee5233 100644 --- a/tests/test_scopes/test_thread_scoped_provider.py +++ b/tests/test_scopes/test_thread_scoped_provider.py @@ -1,8 +1,10 @@ import unittest from queue import Queue from threading import Thread +from unittest.mock import create_autospec from opyoid.bindings import FromCallableProvider +from opyoid.injection_context import InjectionContext from opyoid.scopes.thread_scoped_provider import ThreadScopedProvider @@ -12,7 +14,8 @@ class MyType: class TestThreadScopedProvider(unittest.TestCase): def setUp(self) -> None: - self.class_provider = FromCallableProvider(MyType, [], None, {}) + self.context = create_autospec(InjectionContext, spec_set=True) + self.class_provider = FromCallableProvider(MyType, [], None, {}, self.context) self.provider = ThreadScopedProvider(self.class_provider) def test_get_returns_instance(self): diff --git a/tests_e2e/test_injection.py b/tests_e2e/test_injection.py index 095f887..adb5208 100644 --- a/tests_e2e/test_injection.py +++ b/tests_e2e/test_injection.py @@ -1302,3 +1302,29 @@ class MyOtherClass: self.assertIs(other_instance_2, other_instance_3) self.assertIsNot(other_instance_1, other_instance_4) self.assertIsNot(other_instance_2, other_instance_4) + + def test_dependency_loop_injection(self): + class ClassA: + def __init__(self, my_arg: "ClassB"): + self.my_arg = my_arg + + class ClassB: + def __init__( + self, + ) -> None: + self.my_arg = None + + def __opyoid_post_init__(self, my_arg: ClassA) -> None: + self.my_arg = my_arg + + injector = self.get_injector(ClassA, ClassB) + + instance_a = injector.inject(ClassA) + instance_b = injector.inject(ClassB) + + self.assertIsInstance(instance_a, ClassA) + self.assertIsInstance(instance_a.my_arg, ClassB) + self.assertIsInstance(instance_b, ClassB) + self.assertIsInstance(instance_b.my_arg, ClassA) + self.assertIs(instance_a.my_arg, instance_b) + self.assertIs(instance_b.my_arg, instance_a)