sanic-jwt icon indicating copy to clipboard operation
sanic-jwt copied to clipboard

Duplicate exception handler definition on: route=__ALL_ROUTES__ and exception=<class 'sanic_jwt.exceptions.SanicJWTException'>

Open deffulsFHSS opened this issue 2 years ago • 2 comments

from sanic import Sanic, Blueprint
from sanic_jwt import Initialize

app = Sanic("TestCase")

bp = Blueprint('bp', url_prefix='/api')

app.blueprint(bp)

bp1 = Blueprint('bp1', url_prefix='/test')

app.blueprint(bp1)

Initialize(
    instance=bp,
    app=app,
    authenticate=lambda: True
)

Initialize(
    instance=bp1,
    app=app,
    authenticate=lambda: True
)

print('app', app.router.routes_all.keys())
print('bp', [x.uri for x in bp.routes])

This code gives me following Error. Looks like bug, i don't understand why.

Traceback (most recent call last):
  File "/home/deffuls/Documents/Example/test.py", line 20, in <module>
    Initialize(
  File "/home/deffuls/Documents/Example/env_py/lib/python3.11/site-packages/sanic_jwt/initialization.py", line 137, in __init__
    self.__add_endpoints()
  File "/home/deffuls/Documents/Example/env_py/lib/python3.11/site-packages/sanic_jwt/initialization.py", line 179, in __add_endpoints
    self.app.exception(exceptions.SanicJWTException)(
  File "/home/deffuls/Documents/Example/env_py/lib/python3.11/site-packages/sanic/mixins/exceptions.py", line 37, in decorator
    self._apply_exception_handler(future_exception)
  File "/home/deffuls/Documents/Example/env_py/lib/python3.11/site-packages/sanic/app.py", line 517, in _apply_exception_handler
    self.error_handler.add(exception, handler.handler, route_names)
  File "/home/deffuls/Documents/Example/env_py/lib/python3.11/site-packages/sanic/handlers/error.py", line 73, in add
    self._add((exception, None), handler)
  File "/home/deffuls/Documents/Exampleenv_py/lib/python3.11/site-packages/sanic/handlers/error.py", line 53, in _add
    raise ServerError(message)
sanic.exceptions.ServerError: Duplicate exception handler definition on: route=__ALL_ROUTES__ and exception=<class 'sanic_jwt.exceptions.SanicJWTException'>

deffulsFHSS avatar Jan 20 '24 18:01 deffulsFHSS

I also encountered a Duplicate exception hander definition on: ... exception recently. In my case, I had defined an error handler that attempts to handle the same error twice.

The code that caused the error was this:

  from sanic import Sanic
  from sanic import Request
  from sanic.response import JSONResponse
  from sanic.exceptions import BadRequest
  from sanic.exceptions import InvalidUsage
  from sanic.exceptions import HTTPException


  app = Sanic(app_name, strict_slashes=False)

  @app.exception(BadRequest, InvalidUsage)
  async def handle_authentication_error(request: Request, exception: HTTPException) -> JSONResponse:
      logger.exception(exception)
      return response.json(
          {
              "description": "<some_description>",
              "status": exception.status_code,
              "reason": exception.message
          },
          status=exception.status_code
      )
  
  # rest of the code ...

here BadRequest, and InvalidUsage both points to the same exception definition BadRequest. Got it fixed by removing InvalidUsage, leaving only a single exception handler for a specific exception def.

However, I don't see any custom exception handling done in your code so not really sure what's the issue here.

thisisishara avatar Aug 01 '24 08:08 thisisishara

when you call

Initialize(
    instance=bp,
    app=app,
    authenticate=lambda: True
)

Initialize(
    instance=bp1,
    app=app,
    authenticate=lambda: True
)

Each JWT instance registers a new global handler for SanicJWTException on the same app object. But Sanic’s core rule is:

You can only register one handler per (exception, route) pair.

So the second registration triggers:

ServerError: Duplicate exception handler definition on: route=__ALL_ROUTES__ and exception=<class 'sanic_jwt.exceptions.SanicJWTException'>

I tried to remove duplicated handler by overwriting Initialize function:

class SafeJWTInitialize(Initialize):

    @staticmethod
    def remove_jwt_exception_handler(app):
        """
        Remove existing SanicJWTException handler for the current Sanic app.
        Works for versions where ErrorHandler uses `cached_handlers`.
        """
        key = (SanicJWTException, None)
        handlers_dict = getattr(app.error_handler, "cached_handlers", {})

        if key in handlers_dict:
            del handlers_dict[key]

    """Initialize JWT safely without duplicate exception handler errors."""

    def __init__(self, instance, app=None, **kwargs):
        if app:
            self.remove_jwt_exception_handler(app)
        super().__init__(instance=instance, app=app, **kwargs)

Then, instead of call

Initialize(
    instance=bp,
    app=app,
    authenticate=lambda: True
)

just do

SafeJWTInitialize(
    instance=bp,
    app=app,
    authenticate=lambda: True
)

That automatically removes the duplicate handler each time

It's a bit hacky but should solve the problem.

jeanveau avatar Oct 31 '25 15:10 jeanveau