Metadata-Version: 1.1
Name: PyBinder
Version: 1.5.1
Summary: Python dependency injection and management tool
Home-page: https://github.com/rmk135/pybinder
Author: Roman Mogilatov
Author-email: rmogilatov@gmail.com
License: BSD New
Description: # PyBinder
        Hello world!
        
        I am PyBinder, Python dependency injection and management tool.
        
        You can ask me, what are you interested for. I will serve you well, Master...
         
        ### PyBinder, What is dependency injection?
        
        Master, dependency injection is one of the techniques for making Inversion 
        of Control (IoC). If you are not on the same line with Inversion of Control 
        and Dependency Injection please follow the links:
        
        - http://en.wikipedia.org/wiki/Inversion_of_control
        - http://en.wikipedia.org/wiki/Dependency_injection
        - http://martinfowler.com/articles/injection.html
        - http://stackoverflow.com/questions/130794/what-is-dependency-injection
        
        ### PyBinder, What can you do for my application?
        
        Master, I can:
        
        - bind your application components in pretty form.
        - help you in refactoring your application.
        - make your application easy testable.
        - reduce boilerplate code in your application.
        - make concurrent development easier.
        - help to keep you understanding your application.
        
        
        ### PyBinder, What kind of injections do you support?
        
        I support only constructor injections, Master. There are a lot of discussions 
        about accessor / attributes injections, but this is not my way.
        
        
        ### PyBinder, What versions of Python do you support?
        
        Master, supported Python versions are: 2.6, 2,7, 3.2, 3.3, 3.4
        
        Also I support PyPy 1.9+ and PyPy 3+
        
        ### PyBinder, What are your main ideas?
        
        - My main idea, Master, is that every object in your application should have a key, a string key.
        - Than you should teach me to provide every certain key. 
        - If the objects need to be binded with each other, you also should tell me about this. 
        - After I will know about your objects, how they are provided and binded with each other, I will make an assembly.
        - Than you can ask me to provide certain key and I will give an instance with all dependencies injected.
        
        
        ### PyBinder, How can I install you?
        
        I'm available on PyPi, Master. You can use pip:
        
        `pip install pybinder`
        
        ### PyBinder, How can I define my objects?
        
        Nothing is easier, Master. I'll show you an example:
        
        ```python
        from pybinder import Container
        from pybinder.catalogs import Catalog
        from pybinder.decorators import provides, requires
        from pybinder.providers import Singleton
        
        
        # Here are some example classes, A and B. Class A is a singleton, class B is
        # not.
        class A(object):
            pass
        
        
        class B(object):
            def __init__(self, a):
                self.a = a
        
        
        # First of all, you need a Catalog, Master.
        # I use Catalog classes for defining objects and their dependencies.
        class ExampleCatalog(Catalog):
        
            # Definition of singleton class A.
            # 'object_a' is used as a key.
            @provides('object_a', provider=Singleton)
            def provide_object_a(self):
                return A()
        
            # Definition of factory for class B.
            # Class B factory requires instance of class A, that is defined using
            # 'object_a' key.
            @provides('object_b')
            @requires('object_a')
            def provide_object_b(self, object_a):
                return B(object_a)
        
        # Here I create a Container and put a catalog instance to it.
        container = Container(ExampleCatalog())
        
        # After the container is created, I need to assemble it.
        container.assemble()
        
        # Now I can provide you with class A and B instances.
        object_a = container.provide('object_a')
        object_b_1 = container.provide('object_b')
        object_b_2 = container.provide('object_b')
        
        
        # Also I can inject dependencies to your callbacks, Master:
        @container.inject('object_a')
        @container.inject('object_b')
        def example_callback(object_a, object_b):
            return object_a, object_b
        
        object_a_from_callback, object_b_3 = example_callback()
        
        # I'll make some asserts, to show it works, Master.
        assert object_b_1 is not object_b_2 is not object_b_3
        assert object_b_1.a is object_b_2.a is object_b_3.a is object_a
        assert object_a is object_a_from_callback
        ```
        
        ### PyBinder, What if I want some my objects to be singleton, other are not?
        
        Follow the example, Master:
        
        ```python
        from pybinder import Container
        from pybinder.catalogs import Catalog
        from pybinder.decorators import provides
        from pybinder.providers import Singleton
        
        
        class A(object):
            pass
        
        
        class ExampleCatalog(Catalog):
        
            # Provider will be called every time container needs class A instance.
            @provides('object_a')
            def provide_object_a(self):
                return A()
        
            # Provider will be called once, result of call will be memorized.
            @provides('object_a_singleton', provider=Singleton)
            def provide_object_a_singleton(self):
                return A()
        
        
        # Now, let's make assembled container, Master:
        container = Container(ExampleCatalog())
        container.assemble()
        
        # And ask container to provide us with a few 'object_a' instances:
        object_a_1 = container.provide('object_a')
        object_a_2 = container.provide('object_a')
        
        # And few 'object_a_singleton' instances:
        object_a_singleton_1 = container.provide('object_a_singleton')
        object_a_singleton_2 = container.provide('object_a_singleton')
        
        # Here is an assert, Master:
        assert object_a_1 is not object_a_2  # Factory
        assert object_a_singleton_1 is object_a_singleton_2  # Singleton
        ```
        
        ### PyBinder, I have configured Catalog, but I need to provide an configuration and secret key, what can you offer me?
        
        You can bind them in runtime, Master:
        
        ```python
        from pybinder import Container
        from pybinder.catalogs import Catalog
        from pybinder.providers import Value
        
        # Let's assume that you, Master, have a configured catalog instance.
        catalog = Catalog()
        
        # And you, Master, need to provide some configuration or settings for your
        # objects.
        catalog.bind('db_config', Value({'...': '...'}))
        catalog.bind('secret_key', Value('MHHBF^D*@HF@^FG*@FH'))
        
        container = Container(catalog)
        container.assemble()
        
        assert {'...': '...'} == container.provide('db_config')
        assert 'MHHBF^D*@HF@^FG*@FH' == container.provide('secret_key')
        ```
        
        ### PyBinder, I have a set of objects, I want to use you, what about this case?
        
        No problems with this, Master:
        
        ```python
        from pybinder import Container
        from pybinder.catalogs import Catalog
        from pybinder.providers import Object
        
        # So, you have a set of objects, like below, Master:
        some_object_1 = object()
        some_object_2 = object()
        
        # And you can bind them using my Object provider:
        catalog = Catalog()
        catalog.bind('some_object_1', Object(some_object_1))
        catalog.bind('some_object_2', Object(some_object_2))
        
        container = Container(catalog)
        container.assemble()
        
        assert some_object_1 == container.provide('some_object_1')
        assert some_object_2 == container.provide('some_object_2')
        ```
        
        ### PyBinder, How can I use assembled container?
        
        I'll show you, Master:
        
        ```python
        from pybinder import Container
        from pybinder.catalogs import Catalog
        from pybinder.decorators import provides, requires
        from pybinder.providers import Value, Singleton
        
        
        import sqlite3
        
        
        class A(object):
            def __init__(self, database):
                self.database = database
        
        
        class B(object):
            def __init__(self, database):
                self.database = database
        
        
        class ExampleCatalog(Catalog):
            """
            PyBinder example catalog.
            """
        
            @provides('database', provider=Singleton)
            @requires('db_config')
            def provide_database(self, db_config):
                return sqlite3.connect(**db_config)
        
            @provides('object_a')
            @requires('database')
            def provide_object_a(self, database):
                return A(database)
        
            @provides('object_b')
            @requires('database')
            def provide_object_b(self, database):
                return B(database)
        
        
        catalog = ExampleCatalog()
        catalog.bind('db_config', Value({'database': 'example.db'}))
        container = Container(catalog)
        container.assemble()
        
        # You can use `container.provide()` method, Master:
        a1 = container.provide('object_a')
        b1 = container.provide('object_b')
        
        
        # Or if you have an callback, that needs dependency, please use
        # `container.inject()` decorator, Master:
        @container.inject('object_a')
        @container.inject('object_b')
        def callback1(object_a, object_b):
            print object_a, object_b
        
        
        callback1()
        
        
        # Sometimes, Master, you may wish to make dependency injection with a specific
        # name. I have `with_name` argument in `container.inject()` for such needs:
        @container.inject('object_a', with_name='a')
        @container.inject('object_b', with_name='b')
        def callback2(a, b):
            print a, b
        
        
        callback2()
        
        
        # Sometimes your callbacks need to receive context args, Master.
        # I have no problems with this:
        @container.inject('object_a')
        @container.inject('object_b')
        def callback3(context1, context2, object_a, object_b):
            print context1, context2, object_a, object_b
            return context1, context2
        
        
        context1_original = object()
        context2_original = object()
        
        context1, context2 = callback3(context1_original,
                                       context2=context2_original)
        
        assert context1_original is context1
        assert context2_original is context2
        ```
        
        ### PyBinder, How can I make constructor injection?
        
        You should decorate class with `container.inject()`, Master:
        
        ```python
        from pybinder import Container
        from pybinder.catalogs import Catalog
        from pybinder.decorators import provides
        
        
        class A(object):
            pass
        
        
        class ExampleCatalog(Catalog):
            """
            PyBinder example catalog.
            """
        
            @provides('object_a')
            def provide_object_a(self):
                return A()
        
        
        catalog = ExampleCatalog()
        container = Container(catalog)
        container.assemble()
        
        
        # New instance of class A will be injected for every B instance, Master.
        @container.inject('object_a', with_name='a')
        class B(object):
            def __init__(self, a):
                self.a = a
        
        
        # Let's create several B instances, Master.
        b1 = B()
        b2 = B()
        
        # And make some asserts.
        assert isinstance(b1.a, A)
        assert isinstance(b2.a, A)
        assert b1.a is not b2.a
        ```
        
        ### PyBinder, How can I write unit tests for injection affected callbacks and classes?
        
        Please look at example below, Master:
        
        ```python
        from pybinder import Container
        from pybinder.catalogs import Catalog
        from pybinder.decorators import provides, requires
        from pybinder.providers import Value, Singleton
        
        import sqlite3
        
        
        # Let's assume, Master, that you have made inversion of control of your
        # database connection and used me as a dependency injector.
        
        # Definition part will be as usual, Master:
        class ExampleCatalog(Catalog):
            """
            PyBinder example catalog.
            """
        
            @provides('database', provider=Singleton)
            @requires('db_config')
            def database(self, db_config):
                return sqlite3.connect(**db_config)
        
        
        catalog = ExampleCatalog()
        catalog.bind('db_config', Value({'database': 'example.db'}))
        container = Container(catalog)
        container.assemble()
        
        # Here we will init database with simple schema of users table, Master.
        database = container.provide('database')
        database.execute('CREATE TABLE IF NOT EXISTS users(email text)')
        
        
        # And somewhere in your application you, Master, were needed in implementing
        # function that will check does user with provided email already exist.
        #
        # This function, Master, has required dependency, the database connection,
        # that you will inject into it using `container.inject()`
        @container.inject('database')
        def does_user_exist(email, database):
            cursor = database.execute('SELECT email FROM users WHERE email=?', [email])
            return cursor.fetchone() is not None
        
        
        # Now, Master, I'll show you how you can make some unit tests for
        # `does_user_exist()` function:
        import unittest2 as unittest
        from mock import Mock
        
        
        class DoesUserExistTests(unittest.TestCase):
        
            def test_does_user_exist_row_found(self):
                # We need to create a mock for database connection and cursor, Master.
                database = Mock(sqlite3.Connection)
        
                # Master, cursor mock will return a single row, like user with
                # provided email was founded in database.
                cursor = Mock(sqlite3.Cursor)
                cursor.fetchone.return_value = Mock(sqlite3.Row)
        
                database.execute.return_value = cursor
        
                # Now, Master, let's try to test `does_user_exist()` function,
                # providing our mock as a dependency via keyword argument.
                self.assertTrue(does_user_exist('test@gmail.com', database=database))
        
        
        # The same with the class injection:
        @container.inject('database')
        class UserModel(object):
        
            def __init__(self, database):
                self.database = database
        
            def exists(self, email):
                cursor = self.database.execute('SELECT email FROM users WHERE email=?',
                                               [email])
                return cursor.fetchone() is not None
        
        
        class UserModelTests(unittest.TestCase):
        
            def test_exists_row_found(self):
                database = Mock(sqlite3.Connection)
                cursor = Mock(sqlite3.Cursor)
                cursor.fetchone.return_value = Mock(sqlite3.Row)
                database.execute.return_value = cursor
        
                user = UserModel(database=database)
                self.assertTrue(user.exists('test@gmail.com'))
        ```
        
        The trick here, Master, is that `container.inject()` decorator will prioritize 
        context provided database connection instance to the injected one from 
        container.
        
        The only requirement, Master, is that you should use keyword arguments for 
        providing context specific dependencies.
        
        Worth mentioning, that you, Master, will have no problems with unit testing 
        your application, while you'll be on the same way with Inversion of Control 
        and Dependency Injection.
        
        ### PyBinder, I have a lot of providers from different layers of my application, can you offer me some kind of keys ordering?
        
        Yes, Master. I have a namespaces for such cases. Please, follow the example, Master:
        
        ```python
        from pybinder import Container
        from pybinder.catalogs import Catalog
        from pybinder.decorators import provides, requires
        from pybinder.providers import Singleton
        
        
        # Let's assume, Master that you have 2 modules named Green and Red with a few
        # classes.
        
        # Module Green consists of 2 classes: GreenA and GreenB. Class GreenA has a
        # dependency on class GreenB from current module and class GreenB has a
        # dependency on class RedC from Red module.
        class GreenA(object):
            def __init__(self, b):
                self.b = b
        
        
        class GreenB(object):
            def __init__(self, c):
                self.c = c
        
        
        # Module Red also consists of 2 classes: RedC and RedD. Class RedC has a
        # dependency on class RedD from current module.
        class RedC(object):
            def __init__(self, d):
                self.d = d
        
        
        class RedD(object):
            pass
        
        
        # For such case we will define 2 catalogs for every module: GreenCatalog with
        # definition of classes GreenA and GreenB, and RedCatalog with definition of
        # RedC and RedD.
        # Let's start from green catalog:
        class GreenCatalog(Catalog):
            """
            Green catalog.
            """
        
            # We need to define `namespace` attribute and set namespace name. It can
            # be any valid string, but I recommend to use lower.dotted.case.
            namespace = 'green'
        
            # Than let's define provider for class GreenA, it will be done as usual.
            # Dependency from class GreenB will be defined using `@requires` decorator.
            # `@requires` decorator will bind dependency from current catalog, while
            # another namespace is not defined.
            @provides('a', provider=Singleton)
            @requires('b')
            def provide_a(self, b):
                return GreenA(b)
        
            # While defining provider for class GreenB, we will need to define
            # dependency from class RedC that is defined in red catalog. For such case
            # we will use `@requires` decorator with argument `from_namespace` that
            # will define from what namespace dependency would be imported.
            @provides('b', provider=Singleton)
            @requires('c', from_namespace='red')
            def provide_b(self, c):
                return GreenB(c)
        
        
        # Now, let's proceed with green catalog, nothing special here:
        class RedCatalog(Catalog):
            """
            Red catalog :)
            """
        
            namespace = 'red'
        
            @provides('c', provider=Singleton)
            @requires('d')
            def provide_c(self, d):
                return RedC(d)
        
            @provides('d', provider=Singleton)
            def provide_d(self):
                return RedD()
        
        
        # Now we need to merge both of our catalogs into container. We will do it on
        # create time. After that container should be assembled.
        container = Container(GreenCatalog(), RedCatalog())
        container.assemble()
        
        # Well, now container is ready to use. Let's get some practice.
        
        # First we need to get namespace objects. Namespace objects are used to
        # provide / inject a certain instances from a namespace.
        green = container.namespace('green')
        red = container.namespace('red')
        
        # Now, let's see, how we can provide GreenA and RedC instances:
        a = green.provide('a')
        c = red.provide('c')
        
        assert a.b.c is c
        
        
        # Here is an example of injecting instance from namespace object:
        @green.inject('a')
        @green.inject('b')
        @red.inject('c')
        @red.inject('d')
        class Test1(object):
            def __init__(self, a, b, c, d):
                self.a = a
                self.b = b
                self.c = c
                self.d = d
        
        
        # Container's `@inject` decorator also takes an `from_namespace` argument that
        # defines, from what namespace dependency would be imported.
        @container.inject('a', from_namespace='green')
        @container.inject('b', from_namespace='green')
        @container.inject('c', from_namespace='red')
        @container.inject('d', from_namespace='red')
        class Test2(object):
            def __init__(self, a, b, c, d):
                self.a = a
                self.b = b
                self.c = c
                self.d = d
        
        
        # Now let's make some tests and asserts...
        test1 = Test1()
        test2 = Test2()
        a, b, c, d = test1.a, test1.b, test1.c, test1.d
        
        assert test1.a.b is test2.a.b
        assert test1.b.c is test2.b.c
        assert test1.c.d is test2.c.d
        assert test1.a.b.c.d is test2.a.b.c.d
        ```
        
        ### PyBinder, What do you think about circular dependencies?
        
        Oh, Master, please avoid them. There is a a set of techniques to do that. As for me, I will raise an exception during assembly, if I find circular dependencies.
        
Keywords: Dependency injection,Dependency injection container,DI,DIC,Dependency injector,Inversion of Control,Inversion of Control container,IoC,IoC container
Platform: UNKNOWN
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.6
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.2
Classifier: Programming Language :: Python :: 3.3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Topic :: Software Development
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Libraries :: Python Modules
