Это правильный способ внедрения зависимостей в Django? - PullRequest
6 голосов
/ 11 июня 2019

Я пытаюсь внедрить зависимости в мое представление Django (контроллер?).Вот некоторый фон.

Обычно файл urls.py обрабатывает маршрутизацию.Обычно это что-то вроде этого:

 urlpatterns = [
     path("", views.get_all_posts, name="get_all_posts"),
     path("<int:post_id>", views.get_post, name="get_post"),
     path("create", views.create_post, name="create_post"),
 ]

Проблема в том, что как только вы доберетесь до create_post, например, вы можете зависеть от службы, которая создает сообщения:

# views.py
...

def create_post(self):
    svc = PostCreationService()
    svc.create_post()

Этот тип шаблона трудно проверить.Хотя я знаю, что в библиотеках тестирования Python есть инструменты для насмешек такого рода, я бы предпочел внедрить зависимость в представление.Вот что я придумал.

Класс Controller, имеющий статический метод, export(deps), который принимает список зависимостей и возвращает список объектов шаблона URL:

class ApiController(object):

    @staticmethod
    def export(**deps):
        ctrl = ApiController(**deps)
        return [
            path("", ctrl.get_all_posts, name="get_all_posts"),
            path("<int:post_id>", ctrl.get_post, name="get_post"),
            path("create", ctrl.create_post, name="create_post"),
        ]

    def __init__(self, **deps):
        self.deps = deps

    def get_all_posts():
        pass
    ...

Это выглядит странно, но я не знаю другого способа сделать то, что я пытаюсь сделать.Контроллер должен возвращать список шаблонов URL, а также должен принимать список зависимостей.Используя вышеописанную технику, я могу сделать это в urls.py:

urlpatterns = ApiController.export(foo_service=(lambda x: x))

Теперь я могу свободно использовать foo_service в любом из методов ApiController.

Примечание:

Одной из альтернатив будет то, что конструктор вернет список URL, но я не вижу в этом большого улучшения по сравнению с этим.На самом деле, это кажется мне более запутанным, потому что конструктор класса будет возвращать список вместо экземпляра класса.

Примечание 2:

ЯПомните, что в python есть инструменты для насмешек над учениками.Пожалуйста, не предлагайте их использовать.Я хотел бы использовать DI как способ контроля и управления зависимостями.

Есть идеи, как лучше всего это сделать?

Ответы [ 3 ]

6 голосов
/ 14 июня 2019

Вы можете взглянуть на https://github.com/ets-labs/python-dependency-injector,, но это довольно большая установка.

Вы также можете создать что-то маленькое, например, фабрику сервисов

# services.py
class ServiceFactory:
    def __init__(self):
        self.__services = {}

    def register(self, name, service_class):
        # Maybe add some validation
        self.__services[name] = service_class

    def create(self, name, *args, **kwargs):
        # Maybe add some error handling or fallbacks
        return self.__services[name](*args, **kwargs)

factory = ServiceFactory()


# In your settings.py for example
from services import factory
factory.register('post_creation', PostCreationService)


# Or maybe in apps.ready do auto_load that will loop all apps and get config from services.py


# In your views.py
from services import factory

def create_post(self):
    svc = factory.create('post_creation')
    svc.create_post()


# In your tests.py
from services import factory

def setUp(self):
    factory.register('post_creation', FakePostCreationService)
4 голосов
/ 18 июня 2019

Рассмотрим инъекцию с использованием декораторов:

from functools import wraps

class ServiceInjector:

    def __init__(self):
        self.deps = {}

    def register(self, name=None):

        name = name
        def decorator(thing):
            """
            thing here can be class or function or anything really
            """

            if not name:
                if not hasattr(thing, "__name__"):
                    raise Exception("no name")
                thing_name = thing.__name__
            else:
                thing_name = name
            self.deps[thing_name] = thing
            return thing

        return decorator

    def inject(self, func):

        @wraps(func)
        def decorated(*args, **kwargs):
            new_args = args + (self.deps, )
            return func(*new_args, **kwargs)

        return decorated

# usage:


si = ServiceInjector()

# use func.__name__, registering func
@si.register()
def foo(*args):
    return sum(args)


# we can rename what it's been registered as, here, the class is registered 
# with name `UpperCase` instead of the class name `UpperCaseRepresentation`
@si.register(name="UpperCase")
class UpperCaseRepresentation:
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return self.value.upper()

#register float
si.register(name="PI")(3.141592653)


# inject into functions
@si.inject 
def bar(a, b, c, _deps): # the last one in *args would be receiving the dependencies
    UpperCase, PI, foo = _deps['UpperCase'], _deps['PI'], _deps['foo']
    print(UpperCase('abc')) # ABC
    print(PI) # 3.141592653
    print(foo(a, b, c, 4, 5)) # = 15

bar(1, 2, 3)

# inject into class methods
class Foo:

    @si.inject
    def my_method(self, a, b, _deps, kwarg1=30):
        return _deps['foo'](a, b, kwarg1)

print(Foo().my_method(1, 2, kwarg1=50)) # = 53
0 голосов
/ 19 июня 2019

Вы можете пойти по маршруту колбы и экспортировать экземпляр класса со свойством, которое инициализирует и кэширует службу при первом доступе. Например:

def default_factory():
    pass

# service.py
class ServiceProvider:
    def __init__(self, create_instance=default_factory):
        self.create_instance = create_instance

    _instance = None

    @property
    def service(self):
       if self._instance:
           return self._instance
       self._instance = self.create_instance()
       return self._instance

service_provider = ServiceProvider()
from .service import service_provider

# views.py
def view(request):
    service_provider.service.do_stuff()
    # etc.

Это имеет то преимущество, что легко издеваться и не иметь никакой магии.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...