IoC for Humans
Project description
Inversion of control (IoC) for Humans - written in Python
How to use it
pip install flying-ioc
from flying_ioc import *
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
In the following example, the @NotInject decorator prevents the IoC manager from adding arg1 to the kwargs argument when it initializes the ExampleClass, arg1 argument is needed by the parent class.
Removing the @NotInject decorator in this example will result in an exception.
The @NonInject decorator takes a list of argument names to skip in the initializing process.
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
Exceptions
IoC Manager raises two types of exceptions:
- AttributeError - when trying to get an undefined attribute from the IoC Manager
- TypeError - in the following cases:
- IoC Manager is missing a container definition needed by the initialization of a class or it parent class
- While initializing a class, multiple instances of the same argument are provided to it's parent class - by the user and also injected by IoC Manager. This issue can be resolve using the @NotInject decorator
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(TypeError) 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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file flying_ioc-1.0.3.tar.gz.
File metadata
- Download URL: flying_ioc-1.0.3.tar.gz
- Upload date:
- Size: 6.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/1.13.0 pkginfo/1.5.0.1 requests/2.10.0 setuptools/40.8.0 requests-toolbelt/0.9.1 tqdm/4.31.1 CPython/3.6.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dfc1edb464ca4ff7546512a0c4984555fc752508c9a32cde40598d283023e632
|
|
| MD5 |
fea5d7543ea37f5a284972cb9056e490
|
|
| BLAKE2b-256 |
192ceccef2c59c7af6028737414362b39c0372397571bb49aeff88c23312403c
|
File details
Details for the file flying_ioc-1.0.3-py3-none-any.whl.
File metadata
- Download URL: flying_ioc-1.0.3-py3-none-any.whl
- Upload date:
- Size: 8.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/1.13.0 pkginfo/1.5.0.1 requests/2.10.0 setuptools/40.8.0 requests-toolbelt/0.9.1 tqdm/4.31.1 CPython/3.6.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e984ea521bbfaf7bf10ece1e28a01861c3820b4330a4a130d0ac4750aec4bf24
|
|
| MD5 |
2191fedbdb2a40c37b5b9622c47e6646
|
|
| BLAKE2b-256 |
bcdb5fc3c9079da2dc73221da0fc8033acf8eb3fd6e69173a150dd3873c641b7
|