IoC for Humans
Project description
Inversion of control (IoC) for Humans - written in Python
How to use it
ioc = IocManager()
ioc.set_class(cls=HelperWrapper, singleton=True)
ioc.set_class(cls=GRHelperService, singleton=True)
ioc.set_class(name='api', cls=GRApiClient, singleton=True, thread_local=True)
gr_service: GRHelperService = ioc.GRHelperService
gr_service.start()
Features
- Support getting an object as an attribute of IoC manager
- Initializing a class argument by the argument's class name and if not present by the argument name
- Support for inheritance - initializing arguments needed by parent classes
- Support mapping of values, classes, factories
- Support configuration of mapping like singleton, class per thread
- Support for @NotInject decorator
Attribute
gr_service: GRHelperService = ioc.GRHelperService
gr_service.start()
Initializing a class
class ClassA:
pass
class ClassB:
pass
class ClassC:
pass
class ExampleClass:
def __init__(self, arg1: ClassA, arg2, arg3: ClassC):
assert arg1.__class__ == ClassA
assert arg2.__class__ == ClassB
assert arg3.__class__ == ClassC
def test_arguments():
ioc = IocManager()
ioc.set_class(cls=ClassA)
ioc.set_class(name='arg2', cls=ClassB)
ioc.set_class(name='arg3', cls=ClassC)
ioc.set_class(cls=ExampleClass)
assert ioc.ExampleClass.__class__ == ExampleClass
Support for inheritance
class ClassA:
pass
class ClassB:
pass
class ClassC:
pass
class ParentD:
def __init__(self, arg1: ClassA, **kwargs):
self._arg1 = arg1
class ParentE(ParentD):
def __init__(self, arg2: ClassB, **kwargs):
super().__init__(**kwargs)
self._arg2 = arg2
class ExampleClass(ParentE):
def __init__(self, arg3: ClassC, **kwargs):
super().__init__(**kwargs)
assert self._arg1.__class__ == ClassA
assert self._arg2.__class__ == ClassB
assert arg3.__class__ == ClassC
def test_arguments():
ioc = IocManager()
ioc.set_class(cls=ClassA)
ioc.set_class(cls=ClassB)
ioc.set_class(cls=ClassC)
ioc.set_class(cls=ExampleClass)
assert ioc.ExampleClass.__class__ == ExampleClass
Values
class ClassA:
pass
class ExampleClass:
def __init__(self, value_text, value_class):
assert value_text == 'Some text'
assert value_class.__class__ == ClassA
def test_arguments():
ioc = IocManager()
ioc.set_value(name='value_text', value='Some text')
ioc.set_value(name='value_class', value=ClassA())
ioc.set_class(cls=ExampleClass)
assert ioc.ExampleClass.__class__ == ExampleClass
Factory
class ClassA:
pass
class ClassB:
pass
class ClassC:
pass
class Factory(IocFactory):
@staticmethod
def get_instance(ioc_manager: IocManager, name: str, frame_info: inspect.FrameInfo):
if frame_info.function == 'test_factory_1':
return ioc_manager.ClassA
if name == 'factory1':
return ioc_manager.ClassB
return ioc_manager.ClassC
ioc = IocManager()
ioc.set_class(cls=ClassA)
ioc.set_class(cls=ClassB)
ioc.set_class(cls=ClassC)
ioc.set_factory(name='factory1', cls=Factory)
ioc.set_factory(name='factory2', cls=Factory)
def test_factory_1():
assert ioc.factory1.__class__ == ClassA
assert ioc.factory2.__class__ == ClassA
def test_factory_2():
assert ioc.factory1.__class__ == ClassB
assert ioc.factory2.__class__ == ClassC
Singleton
class ClassA:
pass
class ClassB:
pass
def test_singleton():
ioc = IocManager()
ioc.set_class(cls=ClassA)
ioc.set_class(cls=ClassB, singleton=True)
assert ioc.ClassA != ioc.ClassA
assert ioc.ClassB == ioc.ClassB
Class per thread
class ClassA:
pass
def _set_vars(ioc: IocManager, storage: dict):
def wrapped():
storage['singleton1'] = ioc.singleton1
storage['singleton2'] = ioc.singleton2
return wrapped
def test_class_per_thread():
ioc = IocManager()
ioc.set_class(name='singleton1', cls=ClassA, singleton=True)
ioc.set_class(name='singleton2', cls=ClassA, singleton=True, thread_local=True)
assert ioc.singleton1 == ioc.singleton1
assert ioc.singleton2 == ioc.singleton2
thread_storage = {}
thread = threading.Thread(target=_set_vars(ioc, thread_storage))
thread.start()
thread.join()
assert ioc.singleton1 == thread_storage['singleton1']
assert ioc.singleton2 != thread_storage['singleton2']
@NotInject decorator
class ClassA:
pass
class ClassB:
pass
class Parent:
def __init__(self, arg1: ClassA, **kwargs):
super().__init__(**kwargs)
self._arg1 = arg1
@NotInject(['arg1'])
class ExampleClass(Parent):
def __init__(self, arg2: ClassB, **kwargs):
arg1 = ClassA()
super().__init__(arg1, **kwargs)
assert self._arg1 == arg1
assert arg2.__class__ == ClassB
def test_not_inject():
ioc = IocManager()
ioc.set_class(cls=ClassA)
ioc.set_class(cls=ClassB)
ioc.set_class(cls=ExampleClass)
assert ioc.ExampleClass.__class__ == ExampleClass
If there is no @NotInject, IoC manager will add arg1 to kwargs when it initializes ExampleClass and there will be an exception. To avoid this issue you can use @NotInject decorator and pass a list of argument names to it, to skip initialization of the arguments.
Exceptions
class ClassA:
pass
class ClassB:
pass
class ClassC:
pass
class Parent:
def __init__(self, arg1: ClassA, **kwargs):
super().__init__(**kwargs)
self._arg1 = arg1
class ExampleClass1(Parent):
def __init__(self, arg2: ClassB, **kwargs):
arg1 = ClassA()
super().__init__(arg1, **kwargs)
assert self._arg1 == arg1
assert arg2.__class__ == ClassB
class ExampleClass2:
def __init__(self, arg1: ClassC):
pass
class ExampleClass3(ExampleClass2):
def __init__(self, **kwargs):
super().__init__(**kwargs)
ioc = IocManager()
ioc.set_class(cls=ClassA)
ioc.set_class(cls=ClassB)
def test_exception_container_not_defined():
with pytest.raises(AttributeError) as e:
ioc.NotExists
assert e.value.args[0] == "Name 'NotExists' does not exist"
def test_exception_missing_not_inject():
with pytest.raises(TypeError) as e:
ioc.set_class(cls=ExampleClass1)
ioc.ExampleClass1
assert e.value.args[0] == "__init__() got multiple values for argument 'arg1'"
def test_exception_arg_is_not_defined():
with pytest.raises(RuntimeError) as e:
ioc.set_class(cls=ExampleClass2)
ioc.ExampleClass2
assert e.value.args[0].args[0] == "Can't get a container neither by class name ClassC, neither by arg name arg1"
def test_exception_arg_for_parent_is_not_defined():
with pytest.raises(TypeError) as e:
ioc.set_class(cls=ExampleClass3)
ioc.ExampleClass3
assert e.value.args[0] == "__init__() missing 1 required positional argument: 'arg1'"
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
flying_ioc-1.0.0.tar.gz
(4.0 kB
view hashes)
Built Distribution
Close
Hashes for flying_ioc-1.0.0-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | c80c9279e17a4de4bb6bc2ab614345abf2b5719b82caa86cb54e811c13618d06 |
|
MD5 | 0016d23e17a0fa29b71ab375103da5b6 |
|
BLAKE2b-256 | a9b7ad804b3c4f982bd9f9439c735569386925d9c332644ba3c293d10332b35c |