rafales / injectpy Goto Github PK
View Code? Open in Web Editor NEWDependency injection container for Python (in development)
License: MIT License
Dependency injection container for Python (in development)
License: MIT License
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-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
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.
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:
System.Web.HttpContext.Current
.NET Core DI
Links:
.NET Core has three scopes:
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:
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>();
}
}
We need to run tests on different python versions (3.5+).
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)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.