Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DuplicateServiceRegistrationError: Conflict with RedirectView registration #53

Open
Macktireh opened this issue Dec 29, 2024 · 11 comments
Labels
bug Something isn't working integration Issues relating to interactions with other frameworks

Comments

@Macktireh
Copy link

Hello,

I encountered an issue with WireUp when using it in my Django project. Below is the full error trace:

Traceback (most recent call last):
  File "C:\Users\ABDISOUBANEH\.pyenv\pyenv-win\versions\3.12.7\Lib\threading.py", line 1075, in _bootstrap_inner
    self.run()
  File "C:\Users\ABDISOUBANEH\.pyenv\pyenv-win\versions\3.12.7\Lib\threading.py", line 1012, in run
    self._target(*self._args, **self._kwargs)
  File "E:\develop\projects\saas\locamind\.venv\Lib\site-packages\django\utils\autoreload.py", line 64, in wrapper
    fn(*args, **kwargs)
  File "E:\develop\projects\saas\locamind\.venv\Lib\site-packages\django\core\management\commands\runserver.py", line 126, in inner_run
    autoreload.raise_last_exception()
  File "E:\develop\projects\saas\locamind\.venv\Lib\site-packages\django\utils\autoreload.py", line 87, in raise_last_exception
    raise _exception[1]
  File "E:\develop\projects\saas\locamind\.venv\Lib\site-packages\django\core\management\__init__.py", line 394, in execute
    autoreload.check_errors(django.setup)()
  File "E:\develop\projects\saas\locamind\.venv\Lib\site-packages\django\utils\autoreload.py", line 64, in wrapper
    fn(*args, **kwargs)
  File "E:\develop\projects\saas\locamind\.venv\Lib\site-packages\django\__init__.py", line 24, in setup
    apps.populate(settings.INSTALLED_APPS)
  File "E:\develop\projects\saas\locamind\.venv\Lib\site-packages\django\apps\registry.py", line 124, in populate
    app_config.ready()
  File "E:\develop\projects\saas\locamind\.venv\Lib\site-packages\wireup\integration\django\apps.py", line 48, in ready
    self._autowire(django.urls.get_resolver())
  File "E:\develop\projects\saas\locamind\.venv\Lib\site-packages\wireup\integration\django\apps.py", line 53, in _autowire
    self._autowire(p)
  File "E:\develop\projects\saas\locamind\.venv\Lib\site-packages\wireup\integration\django\apps.py", line 60, in _autowire
    p.callback = self._autowire_class_based_view(target)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "E:\develop\projects\saas\locamind\.venv\Lib\site-packages\wireup\integration\django\apps.py", line 66, in _autowire_class_based_view
    self.container.register(callback.view_class)
  File "E:\develop\projects\saas\locamind\.venv\Lib\site-packages\wireup\ioc\dependency_container.py", line 156, in register
    self._registry.register_service(obj, qualifier, lifetime)
  File "E:\develop\projects\saas\locamind\.venv\Lib\site-packages\wireup\ioc\service_registry.py", line 86, in register_service
    raise DuplicateServiceRegistrationError(klass, qualifier)
wireup.errors.DuplicateServiceRegistrationError: Cannot register type <class 'django.views.generic.base.RedirectView'> with qualifier 'None' as it already exists.

Context

  • Django version: 5.1
  • Python version: 3.12.7
  • WireUp version: 0.15.0
  • OS: Windows
from http import HTTPStatus

from django.contrib import messages
from django.contrib.auth import login
from django.contrib.auth.decorators import login_not_required
from django.http import HttpRequest, HttpResponse, HttpResponsePermanentRedirect, HttpResponseRedirect
from django.shortcuts import redirect, render
from django.urls import reverse
from django.utils.decorators import method_decorator
from django.utils.translation import gettext_lazy as _
from django.views import View

from apps.accounts.forms import LoginForm
from apps.accounts.services.auth import AuthService
from apps.common.exceptions import EmailNotVerifiedError


@method_decorator(decorator=login_not_required, name="dispatch")
class LoginView(View):
    template_name = "accounts/login.html"

    def __init__(self, auth_service: AuthService) -> None:
        self.auth_service = auth_service

    def get(self, request: HttpRequest) -> HttpResponse:
        return render(request=request, template_name=self.template_name, context={"form": LoginForm()})

    def post(self, request: HttpRequest) -> HttpResponseRedirect | HttpResponsePermanentRedirect | HttpResponse:
        form = LoginForm(data=request.POST or None)
        next_path = request.GET.get("next", reverse("dashboard:index"))

        if form.is_valid():
            try:
                user = self.auth_service.login(**form.cleaned_data)
            except EmailNotVerifiedError as e:
                messages.warning(request=request, message=_(e.message))
                return redirect(to="accounts:request_activation")

            if user:
                login(request=request, user=user)
                return redirect(next_path)

            messages.error(request=request, message=_("Email or password incorrect. Please try again."))
        return render(
            request=request, template_name=self.template_name, context={"form": form}, status=HTTPStatus.UNAUTHORIZED
        )

Thank you for your help!

@maldoinc
Copy link
Owner

Hi, I think the issue stems from reusing the same class-based handler for multiple views, such as this, because the same class is registered multiple times.

urlpatterns = [
    path("template_view/foo", TemplateView.as_view(template_name="foo.html")),
    path("template_view/bar", TemplateView.as_view(template_name="bar.html")),
]

In the meantime to unblock yourself you can declare a new class and use that:
Django docs

class FooView(TemplateView):
    template_name = "foo.html"

class BarView(TemplateView):
    template_name = "bar.html"

urlpatterns = [
    path("template_view/foo", FooView.as_view()),
    path("template_view/bar", BarView.as_view()),
]

The WireupConfig._autowire_class_based_view method needs to find a way to tell each of these instances apart from the other. If you find an elegant way to fix the above, contributions are always welcome.

@maldoinc maldoinc added bug Something isn't working integration Issues relating to interactions with other frameworks labels Dec 29, 2024
@maldoinc
Copy link
Owner

maldoinc commented Dec 29, 2024

Hi, I think the changes branch django-reuse-class-fix should resolve this. Can you please install that and confirm it works.

You can use the following command:

pip install git+https://github.com/maldoinc/wireup.git@django-reuse-class-fix

@Macktireh
Copy link
Author

@maldoinc Thank you for your quick response and the proposed workaround! I appreciate the detailed explanation.

In the meantime to unblock yourself you can declare a new class and use that:
Django docs

class FooView(TemplateView):
   template_name = "foo.html"

class BarView(TemplateView):
   template_name = "bar.html"

urlpatterns = [
   path("template_view/foo", FooView.as_view()),
   path("template_view/bar", BarView.as_view()),
]

I wanted to clarify that I am not using TemplateView in my project, and all my views inherit directly from View. For instance, my LoginView is a class-based view inheriting from View and uses dependency injection for services like AuthService. I have also ensured that each view class is uniquely defined and not reused across different routes in the urlpatterns.

You can use the following command:
pip install git+https://github.com/maldoinc/wireup.git@django-reuse-class-fix

Thank you for the update and the fix in the django-reuse-class-fix branch. I will test it as soon as possible and let you know if it resolves the issue.

Since I use PDM as my package manager, I'll check how to install the branch using PDM instead of pip. Once I have tested the changes, I'll report back with the results.

Thanks again for your support, and I appreciate the work you've done on WireUp!

Best regards,
Mack

@Macktireh
Copy link
Author

Macktireh commented Dec 29, 2024

I tested the django-reuse-class-fix branch, but I encountered a new issue during the application startup. Here is the traceback:

Traceback (most recent call last):
  File "C:\Users\ABDISOUBANEH\.pyenv\pyenv-win\versions\3.12.7\Lib\threading.py", line 1075, in _bootstrap_inner
    self.run()
  File "C:\Users\ABDISOUBANEH\.pyenv\pyenv-win\versions\3.12.7\Lib\threading.py", line 1012, in run
    self._target(*self._args, **self._kwargs)
  File "E:\develop\projects\saas\locamind\.venv\Lib\site-packages\django\utils\autoreload.py", line 64, in wrapper
    fn(*args, **kwargs)
  File "E:\develop\projects\saas\locamind\.venv\Lib\site-packages\django\core\management\commands\runserver.py", line 126, in inner_run
    autoreload.raise_last_exception()
  File "E:\develop\projects\saas\locamind\.venv\Lib\site-packages\django\utils\autoreload.py", line 87, in raise_last_exception
    raise _exception[1]
  File "E:\develop\projects\saas\locamind\.venv\Lib\site-packages\django\core\management\__init__.py", line 394, in execute
    autoreload.check_errors(django.setup)()
  File "E:\develop\projects\saas\locamind\.venv\Lib\site-packages\django\utils\autoreload.py", line 64, in wrapper
    fn(*args, **kwargs)
  File "E:\develop\projects\saas\locamind\.venv\Lib\site-packages\django\__init__.py", line 24, in setup
    apps.populate(settings.INSTALLED_APPS)
  File "E:\develop\projects\saas\locamind\.venv\Lib\site-packages\django\apps\registry.py", line 124, in populate
    app_config.ready()
  File "E:\develop\projects\saas\locamind\.venv\Lib\site-packages\wireup\integration\django\apps.py", line 48, in ready
    self._autowire(django.urls.get_resolver())
  File "E:\develop\projects\saas\locamind\.venv\Lib\site-packages\wireup\integration\django\apps.py", line 53, in _autowire
    self._autowire(p)
  File "E:\develop\projects\saas\locamind\.venv\Lib\site-packages\wireup\integration\django\apps.py", line 62, in _autowire
    p.callback = self.container.autowire(target)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "E:\develop\projects\saas\locamind\.venv\Lib\site-packages\wireup\ioc\dependency_container.py", line 213, in autowire
    self._registry.target_init_context(fn)
  File "E:\develop\projects\saas\locamind\.venv\Lib\site-packages\wireup\ioc\service_registry.py", line 140, in target_init_context
    annotated_param = param_get_annotation(parameter, globalns=_get_globals(target))
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "E:\develop\projects\saas\locamind\.venv\Lib\site-packages\wireup\ioc\util.py", line 32, in param_get_annotation
    resolved_type: type[Any] | None = ensure_is_type(parameter.annotation, globalns=globalns)
                                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "E:\develop\projects\saas\locamind\.venv\Lib\site-packages\wireup\ioc\util.py", line 105, in ensure_is_type
    raise WireupError(msg) from e
wireup.errors.WireupError: Using __future__ annotations in Wireup requires the eval_type_backport package to be installed.

It seems that using __future__ annotations in Wireup requires the eval_type_backport package to be installed.

Let me know if there’s anything else I can test or check.

Thanks again for your assistance!

@Macktireh
Copy link
Author

I tried installing the eval_type_backport package, and it seems to have resolved the previous issue. However, I encountered a new problem.

I’m using the django.contrib.auth.middleware.LoginRequiredMiddleware from Django 5, and all my views are protected by default. For views like LoginView, I use the login_not_required decorator as follows:

from django.contrib.auth.decorators import login_not_required
from django.utils.decorators import method_decorator

@method_decorator(decorator=login_not_required, name="dispatch")
class LoginView(View):
    ...

With the LoginRequiredMiddleware active, the server starts correctly, but I’m experiencing excessive redirects, even for unprotected views, and the views do not render. Here is an example of the log with the redirection loop:

[30/Dec/2024 00:05:19] "GET /accounts/login/?next=/accounts/login/%3Fnext%3D/accounts/login/%253Fnext%253D/accounts/login/%25253Fnext%25253D/accounts/login/%2525253Fnext%2525253D/accounts/login/%252525253Fnext%252525253D/accounts/login/%25252525253Fnext%25252525253D/accounts/login/%2525252525253Fnext%2525252525253D/accounts/login/%252525252525253Fnext%252525252525253D/accounts/login/%25252525252525253Fnext%25252525252525253D/accounts/login/%2525252525252525253Fnext%2525252525252525253D/accounts/login/%252525252525252525253Fnext%252525252525252525253D/accounts/login/%25252525252525252525253Fnext%25252525252525252525253D/accounts/login/%2525252525252525252525253Fnext%2525252525252525252525253D/accounts/login/%252525252525252525252525253Fnext%252525252525252525252525253D/accounts/login/%25252525252525252525252525253Fnext%25252525252525252525252525253D/accounts/login/%2525252525252525252525252525253Fnext%2525252525252525252525252525253D/accounts/login/%252525252525252525252525252525253Fnext%252525252525252525252525252525253D/accounts/login/%25252525252525252525252525252525253Fnext%25252525252525252525252525252525253D/accounts/login/%2525252525252525252525252525252525253Fnext%2525252525252525252525252525252525253D/accounts/login/%252525252525252525252525252525252525253Fnext%252525252525252525252525252525252525253D/accounts/login/%25252525252525252525252525252525252525253Fnext%25252525252525252525252525252525252525253D/accounts/login/%2525252525252525252525252525252525252525253Fnext%2525252525252525252525252525252525252525253D/accounts/login/%252525252525252525252525252525252525252525253Fnext%252525252525252525252525252525252525252525253D/accounts/login/%25252525252525252525252525252525252525252525253Fnext%25252525252525252525252525252525252525252525253D/accounts/login/%2525252525252525252525252525252525252525252525253Fnext%2525252525252525252525252525252525252525252525253D/accounts/login/%252525252525252525252525252525252525252525252525253Fnext%252525252525252525252525252525252525252525252525253D/ HTTP/1.1" 302 0
[30/Dec/2024 00:05:19] "GET /accounts/login/?next=/accounts/login/%3Fnext%3D/accounts/login/%253Fnext%253D/accounts/login/%25253Fnext%25253D/accounts/login/%2525253Fnext%2525253D/accounts/login/%252525253Fnext%252525253D/accounts/login/%25252525253Fnext%25252525253D/accounts/login/%2525252525253Fnext%2525252525253D/accounts/login/%252525252525253Fnext%252525252525253D/accounts/login/%25252525252525253Fnext%25252525252525253D/accounts/login/%2525252525252525253Fnext%2525252525252525253D/accounts/login/%252525252525252525253Fnext%252525252525252525253D/accounts/login/%25252525252525252525253Fnext%25252525252525252525253D/accounts/login/%2525252525252525252525253Fnext%2525252525252525252525253D/accounts/login/%252525252525252525252525253Fnext%252525252525252525252525253D/accounts/login/%25252525252525252525252525253Fnext%25252525252525252525252525253D/accounts/login/%2525252525252525252525252525253Fnext%2525252525252525252525252525253D/accounts/login/%252525252525252525252525252525253Fnext%252525252525252525252525252525253D/accounts/login/%25252525252525252525252525252525253Fnext%25252525252525252525252525252525253D/accounts/login/%2525252525252525252525252525252525253Fnext%2525252525252525252525252525252525253D/accounts/login/%252525252525252525252525252525252525253Fnext%252525252525252525252525252525252525253D/accounts/login/%25252525252525252525252525252525252525253Fnext%25252525252525252525252525252525252525253D/accounts/login/%2525252525252525252525252525252525252525253Fnext%2525252525252525252525252525252525252525253D/accounts/login/%252525252525252525252525252525252525252525253Fnext%252525252525252525252525252525252525252525253D/accounts/login/%25252525252525252525252525252525252525252525253Fnext%25252525252525252525252525252525252525252525253D/accounts/login/%2525252525252525252525252525252525252525252525253Fnext%2525252525252525252525252525252525252525252525253D/accounts/login/%252525252525252525252525252525252525252525252525253Fnext%252525252525252525252525252525252525252525252525253D/accounts/login/%25252525252525252525252525252525252525252525252525253Fnext%25252525252525252525252525252525252525252525252525253D/ HTTP/1.1" 302 0
[30/Dec/2024 00:05:19] "GET /accounts/login/?next=/accounts/login/%3Fnext%3D/accounts/login/%253Fnext%253D/accounts/login/%25253Fnext%25253D/accounts/login/%2525253Fnext%2525253D/accounts/login/%252525253Fnext%252525253D/accounts/login/%25252525253Fnext%25252525253D/accounts/login/%2525252525253Fnext%2525252525253D/accounts/login/%252525252525253Fnext%252525252525253D/accounts/login/%25252525252525253Fnext%25252525252525253D/accounts/login/%2525252525252525253Fnext%2525252525252525253D/accounts/login/%252525252525252525253Fnext%252525252525252525253D/accounts/login/%25252525252525252525253Fnext%25252525252525252525253D/accounts/login/%2525252525252525252525253Fnext%2525252525252525252525253D/accounts/login/%252525252525252525252525253Fnext%252525252525252525252525253D/accounts/login/%25252525252525252525252525253Fnext%25252525252525252525252525253D/accounts/login/%2525252525252525252525252525253Fnext%2525252525252525252525252525253D/accounts/login/%252525252525252525252525252525253Fnext%252525252525252525252525252525253D/accounts/login/%25252525252525252525252525252525253Fnext%25252525252525252525252525252525253D/accounts/login/%2525252525252525252525252525252525253Fnext%2525252525252525252525252525252525253D/accounts/login/%252525252525252525252525252525252525253Fnext%252525252525252525252525252525252525253D/accounts/login/%25252525252525252525252525252525252525253Fnext%25252525252525252525252525252525252525253D/accounts/login/%2525252525252525252525252525252525252525253Fnext%2525252525252525252525252525252525252525253D/accounts/login/%252525252525252525252525252525252525252525253Fnext%252525252525252525252525252525252525252525253D/accounts/login/%25252525252525252525252525252525252525252525253Fnext%25252525252525252525252525252525252525252525253D/accounts/login/%2525252525252525252525252525252525252525252525253Fnext%2525252525252525252525252525252525252525252525253D/accounts/login/%252525252525252525252525252525252525252525252525253Fnext%252525252525252525252525252525252525252525252525253D/accounts/login/%25252525252525252525252525252525252525252525252525253Fnext%25252525252525252525252525252525252525252525252525253D/accounts/login/%2525252525252525252525252525252525252525252525252525253Fnext%2525252525252525252525252525252525252525252525252525253D/ HTTP/1.1" 302 0

Chrome Browser:
image

Would you have any insight into why this might be happening?

@maldoinc
Copy link
Owner

No idea sadly.

All the integration should be doing is to essentially wrap the init with the equivalent of @container.autowire to provide the required args but beyond that it should not interact with it. I suspect it might have something to do with the decorator.

I'll try to reproduce it when I can. In the meantime feel free to troubleshoot this, if you can get a fix out quicker I'd be happy to accept the contribution.

@maldoinc
Copy link
Owner

maldoinc commented Jan 2, 2025

Can you reinstall the branch via pip install git+https://github.com/maldoinc/wireup.git@django-reuse-class-fix and see if that fixes things. I was able to reproduce it locally and it seems to work.

@Macktireh
Copy link
Author

Hi,

Thank you for the update! I installed the branch, and it seems to be working well now. The issue appears to be resolved.

I appreciate your effort to reproduce and fix the issue. If I encounter anything else, I'll let you know.
Thanks again for your support!

By the way, when can we expect a new release version?

Best regards,
Mack

@maldoinc
Copy link
Owner

maldoinc commented Jan 4, 2025

This is now included in v0.15.1

@Macktireh
Copy link
Author

Hello,

Wouldn't it be simpler to include eval-type-backport as a dependency of wireup? Otherwise, you end up with this error:

wireup.errors.WireupError: Using __future__ annotations in Wireup requires the eval_type_backport package to be installed.

Thank you!

@maldoinc
Copy link
Owner

maldoinc commented Jan 22, 2025

I didn't want to include it as a dependency because you'll only need it in some edge cases and the error tells you what to do and has its own page in the docs.

It can be an optional dependency perhaps that one can install with pip install wireup[full] or something similar. Other libraries take a similar approach as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working integration Issues relating to interactions with other frameworks
Projects
None yet
Development

No branches or pull requests

2 participants