proofit404 / dependencies Goto Github PK
View Code? Open in Web Editor NEWConstructor injection designed with OOP in mind.
Home Page: https://proofit404.github.io/dependencies/
License: BSD 2-Clause "Simplified" License
Constructor injection designed with OOP in mind.
Home Page: https://proofit404.github.io/dependencies/
License: BSD 2-Clause "Simplified" License
Reduce @model_view_set
scope on create
and update
:
validated_data
should be accessible for create
validated_data
and instance
should be available for update
Remove serializer
instance from the scope.
Hi,
Celery core contributor here.
Thank's for the celery integration.
Would you mind if we documented this integration in our documentation?
Can you issue a PR?
Thanks :)
We can pass Injector
as an argument to the as_view()
call and substitute view class attributes or even methods with DI container.
from dependencies import Injector, Package
app = Package('app')
class Container(Injector):
foo = app.utils.Foo.call
bar = app.libs.decrupt.unsalt_token
# one.py
class Container(Injector):
class SubContainer(Injector):
foo = (this << 2).bar
# two.py
one = Package('one')
class Quiz(Injector):
baz = one.Container.SubContainer.foo
bar = 2
>>> Quiz.baz
2
P.S. ๐ต
from django.core.mail import send_mail
from django.conf import settings
from dependencies import Injector, this
from dependencies.contrib.django import signal, create_view
from .models import Notification, Order
class SendNotification:
def __init__(self, sender, user, order):
self.sender = sender
self.user = user
self.order = order
def receive(self):
notification = Notification.objects.create(order=self.order, customer=self.user)
send_mail(notification.subject, notification.message,
self.sender, [self.user.email])
class CorrectOrder:
def __init__(self, form, finalizer):
self.form = form
self.finalizer = finalizer
def process(self):
order = self.form.save()
self.finalizer.send(order=order)
@create_view
class CreateOrder(Injector):
model_cls = Order
fields = ['price', 'amount']
form_valid = CorrectOrder
sender = settings.NOTIFICATION_SENDER_EMAIL
finalizer = signal('sender', 'user', 'order')
finalizer.connect(SendNotification)
Show available injector scope (injector representation) in the second line of the exception.
>>> App = Injector.let()
>>> App.robot = Robot
>>> App.servo = Servo
>>> App.robot
# Robot instance here
>>> del App.servo
>>> App.robot
# DependencyError here
We can build Injector
attribute or class decorator which will register task and dispatch it to the Injector.run(*args, **kwargs)
.
How can I resolve dependencies that involve async I/O operations such as database connection pool creation?
Do we need to add support for it?
__init__
arguments.operation
arguments.value
arguments.If we define incomplete injector, we will have classic attribute error. User have no clue what happen. They need to check every dependency spec to find what class has missing dependency.
Error message should contain the name of the injectable class we try to build.
It would be really usefull, if Injector could support functions, not only classes.
For example:
def service_1(param):
# expensive calculation
def service_2(service_1):
# expensive_calculation
def process(context, foo):
context.let(service_1=service_1, service_2=service_2)
if foo == 1:
return context.service_1
else:
return context.service_2 + context.service_1
process(Injector.let(param=42), 1)
Basically, this is the same thing as with classes, if a function is assigned to an attribute, then if accessed, this attribute will lazily evaluate function with all dependencies. If same attribute is accessed multiple time, function would be executed only the first time, subsequent attribute access would return cached value.
from dependencies.contrib.django.test import override_route
class PaymentTest(TestCase):
def test_submit(self):
override_route(name='sumbit_payment_view', view_attr=Mock())
from dependencies.contrib.celery.test import override_task
class PaymentTest(TestCase):
def test_submit(self):
override_task(name='canvas.task.name', task_attr=Mock())
What should we do, if someone assigns descriptor instance into the injector attribute?
Basically, that means the user expects access to the Injector
itself.
We should throw an error at the definiton time.
from dependencies import Injector, operation
class Container(Injector):
foo = 1
bar = 2
baz = 3
@operation
def process(foo, bar, baz):
return foo + bar + baz
assert Container.process() == 6
Current proxy implementation requires a lot of eval
and inspect
black magic. This is bad...
We should consider dependency a proxy if:
__dependencies__
attribute with the actual spec.__build__
static method of one argument (injector itself).We will run additional checks during injector subclass creation if:
__check__
static method of two arguments (old scope, new scope).It should be possible to use Package as the namespace in the DI process.
# implementation.py
a = 1
b = 2
c = 3
# container.py
class Container(Package("implementation")):
@operation
def add(a, b, c):
return a + b + c
assert Container.add() == 6
Loop and circle checks happens every time we build new injector.
Even when we access nested injector subclass.
That sucks...
We should run loop checks only if extended scope contains classes.
We should run circle checks only if extended scope contains proxies.
This is the way for mixin
-like dependency injection.
We should not call class constructor on attribute presence check.
hasattr('foo', Container)
# or
'foo' in Container
Probably good use case:
class Container(Injector):
baz = Baz # Depends on foo
TestContainer = Mocker(Container).mock('foo')
class TestMe(TestCase):
def test_me(self):
baz = TestContainer.baz
result = baz.method()
baz.foo.assert_called_with(1)
class TestContainer(Mocker, Injector):
foo = x
baz = Baz
TestContainer.mock('foo').baz # x was mocked with its spec.
Mocker on attribute access will look into __dependencies__
specs, and create an intermediate container with substituted dependencies into something like factory(mock.Mock)
.
Also, we should be able somehow to overwrite presented dependencies from Container. This way they must have mock spec configured against real objects. This also should add arity checks for mocks.
We can't override or disable some dependencies in the pytest named fixtures by default. We can fix this with Injector, a fancy decorator, and a bunch of descriptors.
from dependencies.contrib.pytest import register, require
@register
class Container(Injector):
name = 'fixture_name' # <- Test depending on this fixture will got MyClass instance.
fixture = MyClass
foo = require('fixture_name_1') # <- Inject fixture value into my class constructor.
baz = require('fixture_name_2')
bar = require('fixture_name_3')
class Foo(Injector):
bar = 1
one = Package('one')
class Container(Injector):
bar = one.Foo.bar # varargs error here
from dependencies import Injector, this
from dependencies.contrib.rest_framework import detail_route
from rest_framework.decorators import ModelViewSet
from rest_framework.permissions import IsAuthenticated
from .models import User
from .repositories import CommunityRepo
from .serializers import UserSerializer
class Users(ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
@detail_route
class Container(Injector):
methods = ['get']
permission_classes = [IsAuthenticated]
url_path = r'community_invite/(?P<community_pk>\d+)'
url_name = 'community_invite'
view = this.CommunityRepo.invite_user
CommunityRepo = CommunityRepo
dir
output on Injector
subclasses isn't great
>>> class Foo(Injector):
... x = 1
... y = 2
...
>>> dir(Foo)
['__class__',
'__delattr__',
'__dependencies__',
'__dict__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__gt__',
'__hash__',
'__init__',
'__le__',
'__lt__',
'__module__',
'__ne__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__setattr__',
'__sizeof__',
'__str__',
'__subclasshook__',
'__weakref__',
'let']
Use line_profiler, memory_profiler and py.test benchmark to measure performance every time.
Toc tree at the left side should show contents of all nesting levels.
@injectable
decoratorfrom dependencies import *
class X:
def __init__(self, a):
self.a = a
class Y:
def __init__(self, b):
self.b = b
class Test(Injector):
a = Y
b = X
Test.b
Result
Traceback (most recent call last):
File "/home/proofit404/.pyenv/versions/3.4.0/lib/python3.4/inspect.py", line 971, in getfullargspec
skip_bound_arg=False)
File "/home/proofit404/.pyenv/versions/3.4.0/lib/python3.4/inspect.py", line 1917, in _signature_internal
return Signature.from_function(obj)
File "/home/proofit404/.pyenv/versions/3.4.0/lib/python3.4/inspect.py", line 2450, in from_function
__validate_parameters__=is_duck_function)
File "/home/proofit404/.pyenv/versions/3.4.0/lib/python3.4/inspect.py", line 2366, in __init__
for param in parameters))
File "/home/proofit404/.pyenv/versions/3.4.0/lib/python3.4/collections/__init__.py", line 56, in __init__
self.__update(*args, **kwds)
File "/home/proofit404/.pyenv/versions/3.4.0/lib/python3.4/_collections_abc.py", line 576, in update
if isinstance(other, Mapping):
File "/home/proofit404/.pyenv/versions/3.4.0/lib/python3.4/abc.py", line 182, in __instancecheck__
if subclass in cls._abc_cache:
File "/home/proofit404/.pyenv/versions/3.4.0/lib/python3.4/_weakrefset.py", line 72, in __contains__
wr = ref(item)
RuntimeError: maximum recursion depth exceeded while calling a Python object
Look at pytest Django.
Default context of the form view is limited.
Reimplement form handling logic in each view with form because of necessary context change is a boilerplate...
Suppose we have this code:
domain = Package('domain')
class APIResponse(Injector):
accept = domain.commands.ShowInstanceDetails.show
respond = this.accept
class AcceptInviteView(APIResponse):
accept = services.AcceptInvite.accept
The injector will throw an error with this message
DependencyError: 'accept' is a circular dependency in the 'Link' constructor
This makes situation absolutely unclear and gives the user no clue where to start.
from dependencies import Injector, this, partial
class Container(Injector):
foo = partial(int, this.bar)
bar = '1'
assert Container.foo == 1
Wrap most common view methods, so the user is not forced to wrap them
himself.
ShowInstanceDetails
or serialize_instance
self.view.http_method_not_allowed(self.request)
self.view.permission_denied(self.request)
update_model_instance
validated_data
to generic_api_view
@value
def validated_data(view, request):
serializer = view.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
return serializer.validated_data
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.