GithubHelp home page GithubHelp logo

proofit404 / dependencies Goto Github PK

View Code? Open in Web Editor NEW
359.0 359.0 18.0 4.25 MB

Constructor injection designed with OOP in mind.

Home Page: https://proofit404.github.io/dependencies/

License: BSD 2-Clause "Simplified" License

Python 89.09% JavaScript 0.93% TypeScript 5.74% Shell 4.24%

dependencies's People

Contributors

anatoly-scherbakov avatar dependabot[bot] avatar flerchy avatar hyperleex avatar kevinboyette avatar nicoddemus avatar proofit404 avatar semantic-release-bot avatar sobolevn avatar thedrow avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

dependencies's Issues

Mention integration with Celery in the docs

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 :)

Package proxy.

from dependencies import Injector, Package

app = Package('app')

class Container(Injector):

    foo = app.utils.Foo.call
    bar = app.libs.decrupt.unsalt_token

Injector parent lookup should support Package pointer.

# 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
  • Loop checks
  • Circle checks

P.S. ๐Ÿ˜ต

Add Django Signals example.

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)

Add setattr/delattr support.

>>> App = Injector.let()
>>> App.robot = Robot
>>> App.servo = Servo
>>> App.robot
# Robot instance here
>>> del App.servo
>>> App.robot
# DependencyError here

Add Celery task example.

We can build Injector attribute or class decorator which will register task and dispatch it to the Injector.run(*args, **kwargs).

asyncio support

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?

Attribute error hints.

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.

Possibility to call functions on attribute access

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.

Test helpers to grand access for injectors in the Django url config and Celery app.

Inject Mocker into Django URL config:

from dependencies.contrib.django.test import override_route

class PaymentTest(TestCase):

    def test_submit(self):

        override_route(name='sumbit_payment_view', view_attr=Mock())

Inject Mocker into Celery app

from dependencies.contrib.celery.test import override_task

class PaymentTest(TestCase):

    def test_submit(self):

        override_task(name='canvas.task.name', task_attr=Mock())

Blocked by:

Descriptor support.

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.

Add operation decorator.

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

Proxy protocol.

Current proxy implementation requires a lot of eval and inspect black magic. This is bad...

We should consider dependency a proxy if:

  • it has __dependencies__ attribute with the actual spec.
  • it has __build__ static method of one argument (injector itself).

We will run additional checks during injector subclass creation if:

  • it has __check__ static method of two arguments (old scope, new scope).

Package should work as Injector subclass.

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

Conditional checks.

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.

Mocker class.

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.

Add pytest fixture Injectable example.

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')

Add rest framework contrib.

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

Make dir show injected dependencies.

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']

Improve docs.

  • Describe injections process in the tutorial explicitly
  • Add examples with non class members, it is not straightforward, that you can inject dicts or functions
  • Describe reasons we don't modify original classes with something like @injectable decorator
  • Write primary/secondary database example.

Detect recurtion in constructors.

from 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

Improve circle dependency error message.

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.

Add partial application attributes.

from dependencies import Injector, this, partial

class Container(Injector):

    foo = partial(int, this.bar)
    bar = '1'

assert Container.foo == 1

Add shortcuts to the django rest framework contrib.

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

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.