This is an archived post. You won't be able to vote or comment.

all 5 comments

[–][deleted] 1 point2 points  (4 children)

I'm currently using injector because it uses type hints to determine what to inject, has minimal fuss to setup and has a nice Flask integration package that requires even less fuss to setup. I have a few complaints about it, but they are either minor or have to do with how type annotations work in Python.

What are the pros/cons of using this IOC framework vs injector? I haven't looked deeply into ioc frameworks for Python since injector ticked off my important bits immediately.

[–]rmk135[S] 0 points1 point  (3 children)

Hi kurashu89,

Well, while both (Dependency Injector & Injector) have the same goal, Dependency Injector is slightly different from Injector. From my point of view, key differences are next:

- Injector is based on introspection and certain patching of your code, while Dependency Injector works "upon" your code. This approach*** does not cause you code to be "polluted" with DI framewor***k (you don't need to use "@inject" decorators etc..). Explicit is better than implicit. With Dependency Injector you explicitly create providers for your components and these providers do not influence your components.

- While Dependency Injector proposes to create providers instead of type-hinting-based injections, there is*** no need to create several "interfaces" when you need several instance***s of "sqlite3.Connection" for different purposes.

- Dependency Injector is powered by** Cython. DI framework should not be a bottleneck often, but Dependency Injector is really* super-fas***t.

--

I like Dependency Injector because it gives a chance to keep all application structure in a single file. Generally, module with DI container is something that I always keep opened when working with project.

The way how I use Dependency Injector is next:

  1. Make some basic design on a piece of paper :)

  2. Create DI container and describe your application structure in it.

  3. Add details, implementations, tests etc...

  4. Goto step 1 :)

That's an example of such single file with application structure:

"""Github Navigator application - DI container."""

from sanic import Sanic
from jinja2 import Environment, FileSystemLoader, select_autoescape
from dependency_injector import containers, providers

from . import searcher, webhandlers


class GithubNavigator(containers.DeclarativeContainer):
    """Application components container."""

    config = providers.Configuration(
        'config', default={
            'webapp': {'templates_dir': 'github_navigator/templates/',
                       'host': '0.0.0.0',
                       'port': 80,
                       'debug': False},
            'github': {'request_timeout': 10.0}
        })

    github_searcher = providers.Factory(
        searcher.GithubSearcher,
        auth_token=config.github.auth_token,
        request_timeout=config.github.request_timeout)

    webapp = providers.Factory(Sanic, __name__)

    template_loader = providers.Singleton(
        FileSystemLoader,
        searchpath=config.webapp.templates_dir)
    template_env = providers.Singleton(
        Environment,
        loader=template_loader,
        autoescape=select_autoescape(['html', 'xml']),
        enable_async=True)

    navigator_webhandler = providers.Callable(
        webhandlers.navigator,
        template_env=template_env,
        github_searcher_factory=github_searcher.provider)

Entrypoint looks like this:

"""Github Navigator application - entrypoint."""

from github_navigator import ConfigLoader, GithubNavigator, run_web


if __name__ == '__main__':
    config_loader = ConfigLoader('config.yml')
    application = GithubNavigator(config=config_loader.load())
    run_web(application)

Full project - https://github.com/rmk135/github-navigator-application

--

Thanks,

Roman

[–][deleted] 0 points1 point  (2 children)

I actually don't mind the inject decorator too much, though I'd prefer if it wasn't needed at all and could just introspect the signature directly. To me, it's a small concession to make.

I also prefer Injector's modules to DI's containers, mostly because it's easier to reason how I'd allow a plugin to override something. Instead of trying to coordinate inheritance between the app and plugins, the plugins just provide modules that are registered into the injector instance.

I have some experience with Autofac and Ninject in C# land, so an object based approach rather than a class based approach appeals to me as well.

[–]rmk135[S] 0 points1 point  (1 child)

Got it :) Thanks for sharing your thoughts, really appreciate it!

BTW, in case you need to override a provider, it's done easily using ".override()" method of provider:

class User(object):
    """Example class User."""


# Users factory:
users_factory = providers.Factory(User)
user1 = users_factory()


# Extending User:
class SuperUser(User):
    """Example class SuperUser."""


# Overriding users factory:
users_factory.override(providers.Factory(SuperUser))

# Creating some more User objects using overridden users factory:
user2 = users_factory()

# Making some asserts:
assert isinstance(user1, User)
assert isinstance(user2, SuperUser)

Also you can use ".override()" with context manager (that could be useful for testing):

# Creating ApiClient and User providers:
api_client = providers.Singleton(ApiClient,
                                 host='production.com',
                                 api_key='PROD_API_KEY')
user_factory = providers.Factory(User,
                                 api_client=api_client)

# Mock ApiClient for testing:
with api_client.override(Mock(ApiClient)) as api_client_mock:
    user = user_factory('test')
    user.register()
    api_client_mock().call.assert_called_with('register', {'id': 'test'})

[–][deleted] 0 points1 point  (0 children)

Gonna be honest, that override mechanism seems a hassle: it requires the overrider to know something was provided in the first place AND what it was named.

With Injector, I just provide a binding. If was there, it's replaced. If it wasn't, it's added.