Coder Social home page Coder Social logo

injectpy's Introduction

Hi there ๐Ÿ‘‹

injectpy's People

Contributors

rafales avatar

Stargazers

 avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

injectpy's Issues

Interceptors / activation actions

Sometimes we need to modify the class that container returns. A fine example would be a WebRouter class which needs to know about other classes that acutally implement routes. With interceptors you could plug into class creation process and modify it before it gets injected into the class like this:

from injectpy import Module, intercept

class MyModule(Module):
    @intercept(WebRouter)
    def init_web_router(self, router: WebRouter) -> None:
        router.add(PostsController)
        router.add(PostsAdminController, prefix='/admin')


# somewhere else
kernel = Kernel()
kernel.install(WebModule)
kernel.install(MyModule)

# router has all routes registered through interceptors
router = kernel.get(WebRouter)

Multi-injection

Multi-binding is useful pattern for creating plugin systems. Let's consider:

import abc


# Plugin's interface
class HttpMiddlewarePlugin(abc.ABC):
    @abc.abstractmethod
    def next(self, req: Request, get_response: Callable[[], Response]) -> Response:
        raise NotImplementedError


# First plugin which bans tor users, note that it can accept dependencies
# in __init__ just as any class would do
class BanTorUsers(HttpMiddlewarePlugin):
    def __init__(self, detector: TorDetector) -> None:
        self.detector = detector

    def next(self, req: Request, get_response: Callable[[], Response]) -> Response:
        if self.detector.is_tor_ip(req.ip_address):
            return self.reject()

        return get_response()

    def reject(self) -> Response:
        ...


# Second plugin which disables cache
class DisableCache(HttpMiddlewarePlugin):
    def next(self, req: Request, get_response: Callable[[], Response]) -> Response:
        resp = get_response()
        resp.headers['Cache-Control'] = 'no-cache'
        return resp


# Now how we use this: all you need to do is to accept list of instances:
class HttpHandler:
    def __init__(self, plugins: List[HttpMiddlewarePlugin]) -> None:
        self.plugins = plugins

    def _handle_request(self, req: Request) -> Response:
        # handles the request after middleware
        ...

    def handle(self, req: Request) -> Response:
        get_response = partial(self._handle_request, req)

        for plugin in reversed(self.plugins):
            get_response = partial(plugin.next, req, get_response)

        return get_response()


container.multibind(HttpMiddlewarePlugin, to=DisableCache)
container.multibind(HttpMiddlewarePlugin, to=BanTorUsers)

handler = container.get(HttpHandler)
resp = handler.handle(FakeRequest('/'))

Note: while guice supports weird patterns for doing this - we want
only to support list of bindings.

If you need a more advanced pattern you can use factories and/or combine
them with interceptors.

Inspiration: https://github.com/ninject/Ninject/wiki/Multi-injection

Scopes

All containers have to deal with scoping issue - or how long should instances live. Many of them have different ideas about how it should happen.

Prior work

Ninject

Scopes in Ninject:

Ninject uses "cache and collect" system which you can read more about here: https://nkohari.wordpress.com/2009/03/06/cache-and-collect-lifecycle-management-in-ninject-20/

Basically Ninject associates lifecycle of a scope with an object. When object is collected by GC Ninject will detect that and dispose of all objects for given scope:

  • For singleton Ninject associates objects with the kernel itself. So when kernel is gone - so are the objects from singleton scope.
    *For thread scope it uses thread itself.
  • For request scope by default it uses System.Web.HttpContext.Current
  • It also has "transient" scope which basically means creating new instance every time one is requested

.NET Core DI

Links:

.NET Core has three scopes:

  • transient: just creates new class every time one is requeted
  • singleton: one instance per DI Container
  • scoped: instances live as long as a scope (mostly request, but you can create your own)

First two are pretty easy, scoped is automatically managed by MVC framework. If you want to manage it yourself (eg. call it from command line utility) you can do it like this:

using (var serviceScope = host.Services.CreateScope())
    {
        var services = serviceScope.ServiceProvider;

        try
        {
            var serviceContext = services.GetRequiredService<MyScopedService>();
            // Use the context here
        }
        catch (Exception ex)
        {
            var logger = services.GetRequiredService<ILogger<Program>>();
            logger.LogError(ex, "An error occurred.");
        }
    }

Autofac

Autofac is similar to .NET Core DI. It has these scopes:

  • "Instance Per Dependency" - equal to transient
  • "Single Instance" - singleton
  • "Instance Per Lifetime Scope" - similar to "scoped"
  • thread/request - built on lifetime scopes

Here's how to create a scope:

using(var scope1 = container.BeginLifetimeScope())
{
  for(var i = 0; i < 100; i++)
  {
    // Every time you resolve this from within this
    // scope you'll get the same instance.
    var w1 = scope1.Resolve<Worker>();
  }
}

Disposing / cleaning up

Some instances must be cleaned up. C# has a concept of "disposing" which works great with ninject. Python has different mechanism - with (or __enter__ / __exit__). But that would not work as equally good.

Idea: just add dispose param:

# Option 1:
kernel.bind(Session, factory=make_session, dispose=lambda session: session.close())

# Option 2:
@contextlib.contextmanager
def make_session():
    session = Session()
    try:
        yield session
    finally:
        session.close()

container.bind(Session, to=make_session, dispose=True)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.